Validate elicitation sub-capabilities in check_capability#3061
Validate elicitation sub-capabilities in check_capability#3061Rodrigo-Palma wants to merge 2 commits into
Conversation
check_capability only rejected a requested elicitation capability when the client declared no elicitation support at all, ignoring the form/url sub-capabilities. A client that advertised only URL-mode elicitation would incorrectly pass a check for form-mode elicitation (and vice versa). Mirror the existing sampling sub-capability handling: when a specific elicitation mode is requested, require the client to declare that mode. A bare elicitation request is still satisfied by any elicitation support. Fixes modelcontextprotocol#2965
Document that check_client_capability inspects the elicitation form/url sub-capabilities, so a tool can choose a mode the client actually supports before it asks. Companion to the check_capability sub-capability fix.
bddfe3c to
22da7a0
Compare
|
Reviewed against current `upstream/main`'s `connection.py`: before this change, `check_capability` only checked `have.elicitation is None`, so a server calling `ctx.session.check_client_capability(ClientCapabilities(elicitation=ElicitationCapability(form=...)))` against a client that declared only `url`-mode elicitation would get `True` back — a false positive, since that client can't actually render a form. The fix's per-submode check (`form`/`url`) mirrors the existing pattern already used two lines above for `sampling.tools`, so it's consistent with how this method handles other nested sub-capabilities rather than introducing a new pattern. The added test matrix in `test_connection.py` covers exactly the cases that matter: bare `ElicitationCapability()` still matches any elicitation support (no regression for existing callers that don't care about the mode), form-only vs url-only client in both directions correctly returns `False`, and matching submodes return `True`. That's the right shape for a capability-negotiation fix — it's easy to get "any None sub-field is a wildcard" backwards, and the test cases confirm the None-vs-declared-vs-mismatched semantics are all handled correctly. The docs addition (`elicitation.md`) is a nice inclusion — without it, `check_client_capability` with a specific submode isn't obviously in scope for a tool author skimming the elicitation guide. CI green across the matrix. Looks correct to me. |
Connection.check_capability()(exposed publicly viaServerSession.check_client_capability) rejected a requestedelicitationcapability only when the client declared no elicitation support at all. It never inspected theform/urlsub-capabilities, so a client that advertised only URL-mode elicitation would incorrectly pass a check for form-mode elicitation, and vice versa.Handle elicitation the same way as
sampling: when a specific mode is requested, require the client to declare that mode. A bare elicitation request is still satisfied by any elicitation support.Fixes #2965
Motivation and Context
check_capabilitymirrors the (correct)samplinghandling for the top-level presence check but omits the per-sub-capability checks thatsamplingperforms forcontext/tools:Because it only returns
Falsewhen the client has no elicitation at all, a caller checking for form-mode elicitation against a URL-only client (or vice versa) gets a wrongTrue. This is public API, so once a caller relies on it (e.g. checking for form support beforeelicit_form), it silently returns incorrect results.The fix adds the
form/urlsub-capability checks, matching thesamplingpattern:Scope kept to elicitation. The issue also mentioned
extensionsandtasksas lower-priority gaps:extensionsis already handled onmain(SEP-2133), andtasksis left out to keep this tightly scoped — happy to follow up separately if desired.How Has This Been Tested?
Extended the existing
test_check_capability_per_field_branchesparametrization intests/server/test_connection.pywith the elicitation branches: the two bug cases (URL-only client vs. form request, and form-only client vs. URL request), the matching positive cases, and the bare-request case. The two bug cases fail onmainand pass with this change; the fulltest_connection.pysuite is green (44 passed).ruff check,ruff format --check, andpyrightare all clean on the changed files.Docs: added a short "Ask only in a mode the client supports" section to
docs/handlers/elicitation.mdshowing how a server usescheck_client_capabilityto pick a form/url mode the client supports before eliciting.mkdocs build --strictpasses.Breaking Changes
No API change.
check_capabilitynow returns the correctFalsefor a requested elicitation mode the client does not support (previously a wrongTrue). Callers that were (incorrectly) relying on the old always-true-when-any-elicitation behavior would see the corrected result.Types of changes
Checklist
Additional context
AI assistance: I used AI tooling to help analyze the issue, draft the fix, and scaffold the tests. I reviewed and understand the change, stand behind it, and can discuss it directly.