Add MCP server functions for repair commands (bd-7bbc4e6a)
- Add repair_deps(fix=False) for orphaned dependencies - Add detect_pollution(clean=False) for test issue detection - Add validate(checks=None, fix_all=False) for health checks - Implemented in BdCliClient, stubs in BdDaemonClient - Registered as MCP tools in server.py Amp-Thread-ID: https://ampcode.com/threads/T-9ce04c75-201b-4019-b9f1-0cf10663557c Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -139,6 +139,43 @@ class BdClientBase(ABC):
|
||||
"""Get current database schema for inspection."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def repair_deps(self, fix: bool = False) -> dict:
|
||||
"""Find and optionally fix orphaned dependency references.
|
||||
|
||||
Args:
|
||||
fix: If True, automatically remove orphaned dependencies
|
||||
|
||||
Returns:
|
||||
Dict with orphans_found, orphans list, and fixed count if fix=True
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def detect_pollution(self, clean: bool = False) -> dict:
|
||||
"""Detect test issues that leaked into production database.
|
||||
|
||||
Args:
|
||||
clean: If True, delete detected test issues
|
||||
|
||||
Returns:
|
||||
Dict with detected test issues and deleted count if clean=True
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
|
||||
"""Run database validation checks.
|
||||
|
||||
Args:
|
||||
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
|
||||
fix_all: If True, auto-fix all fixable issues
|
||||
|
||||
Returns:
|
||||
Dict with validation results for each check
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BdCliClient(BdClientBase):
|
||||
"""Client for calling bd CLI commands and parsing JSON output."""
|
||||
@@ -623,6 +660,63 @@ class BdCliClient(BdClientBase):
|
||||
raise BdCommandError("Invalid response for get_schema_info")
|
||||
return data
|
||||
|
||||
async def repair_deps(self, fix: bool = False) -> dict:
|
||||
"""Find and optionally fix orphaned dependency references.
|
||||
|
||||
Args:
|
||||
fix: If True, automatically remove orphaned dependencies
|
||||
|
||||
Returns:
|
||||
Dict with orphans_found, orphans list, and fixed count if fix=True
|
||||
"""
|
||||
args = ["repair-deps"]
|
||||
if fix:
|
||||
args.append("--fix")
|
||||
|
||||
data = await self._run_command(*args)
|
||||
if not isinstance(data, dict):
|
||||
raise BdCommandError("Invalid response for repair-deps")
|
||||
return data
|
||||
|
||||
async def detect_pollution(self, clean: bool = False) -> dict:
|
||||
"""Detect test issues that leaked into production database.
|
||||
|
||||
Args:
|
||||
clean: If True, delete detected test issues
|
||||
|
||||
Returns:
|
||||
Dict with detected test issues and deleted count if clean=True
|
||||
"""
|
||||
args = ["detect-pollution"]
|
||||
if clean:
|
||||
args.extend(["--clean", "--yes"])
|
||||
|
||||
data = await self._run_command(*args)
|
||||
if not isinstance(data, dict):
|
||||
raise BdCommandError("Invalid response for detect-pollution")
|
||||
return data
|
||||
|
||||
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
|
||||
"""Run database validation checks.
|
||||
|
||||
Args:
|
||||
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
|
||||
fix_all: If True, auto-fix all fixable issues
|
||||
|
||||
Returns:
|
||||
Dict with validation results for each check
|
||||
"""
|
||||
args = ["validate"]
|
||||
if checks:
|
||||
args.extend(["--checks", checks])
|
||||
if fix_all:
|
||||
args.append("--fix-all")
|
||||
|
||||
data = await self._run_command(*args)
|
||||
if not isinstance(data, dict):
|
||||
raise BdCommandError("Invalid response for validate")
|
||||
return data
|
||||
|
||||
async def init(self, params: InitParams | None = None) -> str:
|
||||
"""Initialize bd in current directory.
|
||||
|
||||
|
||||
@@ -452,6 +452,49 @@ class BdDaemonClient(BdClientBase):
|
||||
"""
|
||||
raise NotImplementedError("get_schema_info not supported via daemon - use CLI client")
|
||||
|
||||
async def repair_deps(self, fix: bool = False) -> dict:
|
||||
"""Find and optionally fix orphaned dependency references.
|
||||
|
||||
Args:
|
||||
fix: If True, automatically remove orphaned dependencies
|
||||
|
||||
Returns:
|
||||
Dict with orphans_found, orphans list, and fixed count if fix=True
|
||||
|
||||
Note:
|
||||
This falls back to CLI since repair operations are rare
|
||||
"""
|
||||
raise NotImplementedError("repair_deps not supported via daemon - use CLI client")
|
||||
|
||||
async def detect_pollution(self, clean: bool = False) -> dict:
|
||||
"""Detect test issues that leaked into production database.
|
||||
|
||||
Args:
|
||||
clean: If True, delete detected test issues
|
||||
|
||||
Returns:
|
||||
Dict with detected test issues and deleted count if clean=True
|
||||
|
||||
Note:
|
||||
This falls back to CLI since pollution detection is a rare operation
|
||||
"""
|
||||
raise NotImplementedError("detect_pollution not supported via daemon - use CLI client")
|
||||
|
||||
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
|
||||
"""Run database validation checks.
|
||||
|
||||
Args:
|
||||
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
|
||||
fix_all: If True, auto-fix all fixable issues
|
||||
|
||||
Returns:
|
||||
Dict with validation results for each check
|
||||
|
||||
Note:
|
||||
This falls back to CLI since validation is a rare operation
|
||||
"""
|
||||
raise NotImplementedError("validate not supported via daemon - use CLI client")
|
||||
|
||||
async def add_dependency(self, params: AddDependencyParams) -> None:
|
||||
"""Add a dependency between issues.
|
||||
|
||||
|
||||
@@ -19,16 +19,19 @@ from beads_mcp.tools import (
|
||||
beads_blocked,
|
||||
beads_close_issue,
|
||||
beads_create_issue,
|
||||
beads_detect_pollution,
|
||||
beads_get_schema_info,
|
||||
beads_init,
|
||||
beads_inspect_migration,
|
||||
beads_list_issues,
|
||||
beads_quickstart,
|
||||
beads_ready_work,
|
||||
beads_repair_deps,
|
||||
beads_reopen_issue,
|
||||
beads_show_issue,
|
||||
beads_stats,
|
||||
beads_update_issue,
|
||||
beads_validate,
|
||||
current_workspace, # ContextVar for per-request workspace routing
|
||||
)
|
||||
|
||||
@@ -602,6 +605,53 @@ async def get_schema_info(workspace_root: str | None = None) -> dict:
|
||||
return await beads_get_schema_info()
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
name="repair_deps",
|
||||
description="Find and optionally fix orphaned dependency references.",
|
||||
)
|
||||
@with_workspace
|
||||
async def repair_deps(fix: bool = False, workspace_root: str | None = None) -> dict:
|
||||
"""Find and optionally fix orphaned dependency references.
|
||||
|
||||
Scans all issues for dependencies pointing to non-existent issues.
|
||||
Returns orphaned dependencies and optionally removes them with fix=True.
|
||||
"""
|
||||
return await beads_repair_deps(fix=fix)
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
name="detect_pollution",
|
||||
description="Detect test issues that leaked into production database.",
|
||||
)
|
||||
@with_workspace
|
||||
async def detect_pollution(clean: bool = False, workspace_root: str | None = None) -> dict:
|
||||
"""Detect test issues that leaked into production database.
|
||||
|
||||
Detects test issues using pattern matching (titles starting with 'test', etc.).
|
||||
Returns detected test issues and optionally deletes them with clean=True.
|
||||
"""
|
||||
return await beads_detect_pollution(clean=clean)
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
name="validate",
|
||||
description="Run comprehensive database health checks.",
|
||||
)
|
||||
@with_workspace
|
||||
async def validate(
|
||||
checks: str | None = None,
|
||||
fix_all: bool = False,
|
||||
workspace_root: str | None = None,
|
||||
) -> dict:
|
||||
"""Run comprehensive database health checks.
|
||||
|
||||
Available checks: orphans, duplicates, pollution, conflicts.
|
||||
If checks is None, runs all checks.
|
||||
Returns validation results for each check.
|
||||
"""
|
||||
return await beads_validate(checks=checks, fix_all=fix_all)
|
||||
|
||||
|
||||
async def async_main() -> None:
|
||||
"""Async entry point for the MCP server."""
|
||||
await mcp.run_async(transport="stdio")
|
||||
|
||||
@@ -484,6 +484,60 @@ async def beads_get_schema_info() -> dict:
|
||||
return await client.get_schema_info()
|
||||
|
||||
|
||||
async def beads_repair_deps(
|
||||
fix: Annotated[bool, "If True, automatically remove orphaned dependencies"] = False,
|
||||
) -> dict:
|
||||
"""Find and optionally fix orphaned dependency references.
|
||||
|
||||
Scans all issues for dependencies pointing to non-existent issues.
|
||||
Returns orphaned dependencies and optionally removes them with fix=True.
|
||||
|
||||
Returns dict with:
|
||||
- orphans_found: number of orphaned dependencies
|
||||
- orphans: list of orphaned dependency details
|
||||
- fixed: number of orphans fixed (if fix=True)
|
||||
"""
|
||||
client = await _get_client()
|
||||
return await client.repair_deps(fix=fix)
|
||||
|
||||
|
||||
async def beads_detect_pollution(
|
||||
clean: Annotated[bool, "If True, delete detected test issues"] = False,
|
||||
) -> dict:
|
||||
"""Detect test issues that leaked into production database.
|
||||
|
||||
Detects test issues using pattern matching:
|
||||
- Titles starting with 'test', 'benchmark', 'sample', 'tmp', 'temp'
|
||||
- Sequential numbering (test-1, test-2, ...)
|
||||
- Generic descriptions or no description
|
||||
- Created in rapid succession
|
||||
|
||||
Returns dict with detected test issues and deleted count if clean=True.
|
||||
"""
|
||||
client = await _get_client()
|
||||
return await client.detect_pollution(clean=clean)
|
||||
|
||||
|
||||
async def beads_validate(
|
||||
checks: Annotated[str | None, "Comma-separated list of checks (orphans,duplicates,pollution,conflicts)"] = None,
|
||||
fix_all: Annotated[bool, "If True, auto-fix all fixable issues"] = False,
|
||||
) -> dict:
|
||||
"""Run comprehensive database health checks.
|
||||
|
||||
Available checks:
|
||||
- orphans: Orphaned dependencies (references to deleted issues)
|
||||
- duplicates: Duplicate issues (identical content)
|
||||
- pollution: Test pollution (leaked test issues)
|
||||
- conflicts: Git merge conflicts in JSONL
|
||||
|
||||
If checks is None, runs all checks.
|
||||
|
||||
Returns dict with validation results for each check.
|
||||
"""
|
||||
client = await _get_client()
|
||||
return await client.validate(checks=checks, fix_all=fix_all)
|
||||
|
||||
|
||||
async def beads_init(
|
||||
prefix: Annotated[str | None, "Issue prefix (e.g., 'myproject' for myproject-1, myproject-2)"] = None,
|
||||
) -> str:
|
||||
|
||||
Reference in New Issue
Block a user