Fix elicitation graceful degradation when client lacks protocol support

ctx.elicit() throws an exception (not CancelledElicitation) when the
MCP client doesn't implement the elicitation/create JSON-RPC method.
Wrap the call in try/except to treat protocol-level rejection the
same as CancelledElicitation. Found during live testing with Claude
Code CLI which doesn't support MCP elicitation yet.
This commit is contained in:
Ryan Malloy 2026-03-06 12:04:49 -07:00
parent 5fa1eb36ef
commit 89419a36c2
2 changed files with 28 additions and 7 deletions

View File

@ -25,6 +25,7 @@ from mcdbus._state import mcp
async def _confirm_or_abort(ctx: Context, message: str, operation: str) -> None:
"""Elicit user confirmation; raise ToolError or return silently to proceed."""
try:
result = await ctx.elicit(
message,
{
@ -32,6 +33,11 @@ async def _confirm_or_abort(ctx: Context, message: str, operation: str) -> None:
"deny": {"title": "No, cancel"},
},
)
except Exception:
# Client doesn't implement elicitation at the protocol level —
# ctx.elicit() throws instead of returning CancelledElicitation.
result = CancelledElicitation()
if isinstance(result, AcceptedElicitation) and result.data == "confirm":
return
if isinstance(result, CancelledElicitation):

View File

@ -48,6 +48,21 @@ class TestConfirmOrAbort:
with pytest.raises(ToolError, match="Elicitation required"):
await _confirm_or_abort(ctx, "Test message", "test_op")
async def test_elicit_exception_treated_as_cancelled(self):
"""When ctx.elicit() throws (client lacks protocol support), proceed."""
ctx = AsyncMock()
ctx.elicit = AsyncMock(side_effect=Exception("Method not found"))
# Should not raise — exception is treated as CancelledElicitation
await _confirm_or_abort(ctx, "Test message", "test_op")
async def test_elicit_exception_hard_fails_when_required(self):
"""When ctx.elicit() throws and MCDBUS_REQUIRE_ELICITATION is set, fail."""
ctx = AsyncMock()
ctx.elicit = AsyncMock(side_effect=Exception("Method not found"))
with patch.dict(os.environ, {"MCDBUS_REQUIRE_ELICITATION": "1"}):
with pytest.raises(ToolError, match="Elicitation required"):
await _confirm_or_abort(ctx, "Test message", "test_op")
class TestCallMethodElicitation:
"""Verify call_method triggers elicitation on system bus but not session bus."""