MCP: Smart routing for lifecycle status changes in update tool

- update(status="closed") now routes to close() tool
- update(status="open") now routes to reopen() tool
- Respects Claude Code approval workflows for lifecycle events
- Prevents bypass of approval settings
- bd CLI remains unopinionated; routing only in MCP layer
- Users can safely auto-approve benign updates without exposing closure bypass

Fixes #123

Amp-Thread-ID: https://ampcode.com/threads/T-8b85a68e-7e06-460e-9840-9c6b6a6b7e85
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-23 14:10:33 -07:00
parent 68071df49a
commit feac3f86e7
4 changed files with 74 additions and 2 deletions

View File

@@ -360,6 +360,54 @@ async def test_update_issue_multiple_fields(sample_issue):
mock_client.update.assert_called_once()
@pytest.mark.asyncio
async def test_update_issue_routes_closed_to_close(sample_issue):
"""Test that update with status=closed routes to close tool."""
closed_issue = sample_issue.model_copy(
update={"status": "closed", "closed_at": "2024-01-02T00:00:00Z"}
)
mock_client = AsyncMock()
mock_client.close = AsyncMock(return_value=[closed_issue])
with patch("beads_mcp.tools._get_client", return_value=mock_client):
result = await beads_update_issue(
issue_id="bd-1",
status="closed",
notes="Task completed"
)
# Should route to close, not update
assert isinstance(result, list)
assert len(result) == 1
assert result[0].status == "closed"
mock_client.close.assert_called_once()
mock_client.update.assert_not_called()
@pytest.mark.asyncio
async def test_update_issue_routes_open_to_reopen(sample_issue):
"""Test that update with status=open routes to reopen tool."""
reopened_issue = sample_issue.model_copy(
update={"status": "open", "closed_at": None}
)
mock_client = AsyncMock()
mock_client.reopen = AsyncMock(return_value=[reopened_issue])
with patch("beads_mcp.tools._get_client", return_value=mock_client):
result = await beads_update_issue(
issue_id="bd-1",
status="open",
notes="Needs more work"
)
# Should route to reopen, not update
assert isinstance(result, list)
assert len(result) == 1
assert result[0].status == "open"
mock_client.reopen.assert_called_once()
mock_client.update.assert_not_called()
@pytest.mark.asyncio
async def test_beads_stats():
"""Test beads_stats tool."""