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:
parent
5fa1eb36ef
commit
89419a36c2
@ -25,6 +25,7 @@ from mcdbus._state import mcp
|
|||||||
|
|
||||||
async def _confirm_or_abort(ctx: Context, message: str, operation: str) -> None:
|
async def _confirm_or_abort(ctx: Context, message: str, operation: str) -> None:
|
||||||
"""Elicit user confirmation; raise ToolError or return silently to proceed."""
|
"""Elicit user confirmation; raise ToolError or return silently to proceed."""
|
||||||
|
try:
|
||||||
result = await ctx.elicit(
|
result = await ctx.elicit(
|
||||||
message,
|
message,
|
||||||
{
|
{
|
||||||
@ -32,6 +33,11 @@ async def _confirm_or_abort(ctx: Context, message: str, operation: str) -> None:
|
|||||||
"deny": {"title": "No, cancel"},
|
"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":
|
if isinstance(result, AcceptedElicitation) and result.data == "confirm":
|
||||||
return
|
return
|
||||||
if isinstance(result, CancelledElicitation):
|
if isinstance(result, CancelledElicitation):
|
||||||
|
|||||||
@ -48,6 +48,21 @@ class TestConfirmOrAbort:
|
|||||||
with pytest.raises(ToolError, match="Elicitation required"):
|
with pytest.raises(ToolError, match="Elicitation required"):
|
||||||
await _confirm_or_abort(ctx, "Test message", "test_op")
|
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:
|
class TestCallMethodElicitation:
|
||||||
"""Verify call_method triggers elicitation on system bus but not session bus."""
|
"""Verify call_method triggers elicitation on system bus but not session bus."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user