From 51040625113144fd56800d08c6c7f716f16d2703 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 26 Jun 2026 01:32:20 +0000 Subject: [PATCH] fix(server): validate elicitation form/url sub-capabilities in check_capability Connection.check_capability() only verified that the client declared elicitation at all, not whether specific sub-capabilities (form vs url) matched. Mirror the existing sampling sub-capability checks. Fixes #2965 --- src/mcp/server/connection.py | 9 +++++++-- tests/server/test_connection.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/connection.py b/src/mcp/server/connection.py index 76917f896..bf990ebd0 100644 --- a/src/mcp/server/connection.py +++ b/src/mcp/server/connection.py @@ -337,8 +337,13 @@ def check_capability(self, capability: ClientCapabilities) -> bool: return False if capability.sampling.tools is not None and have.sampling.tools is None: return False - if capability.elicitation is not None and have.elicitation is None: - return False + if capability.elicitation is not None: + if have.elicitation is None: + return False + if capability.elicitation.form is not None and have.elicitation.form is None: + return False + if capability.elicitation.url is not None and have.elicitation.url is None: + return False if capability.experimental is not None: if have.experimental is None: return False diff --git a/tests/server/test_connection.py b/tests/server/test_connection.py index d448905a9..49a1eeac9 100644 --- a/tests/server/test_connection.py +++ b/tests/server/test_connection.py @@ -21,6 +21,7 @@ CreateMessageRequestParams, ElicitationCapability, EmptyResult, + FormElicitationCapability, Implementation, ListRootsRequest, ListRootsResult, @@ -31,6 +32,7 @@ SamplingCapability, SamplingContextCapability, SamplingToolsCapability, + UrlElicitationCapability, ) from mcp_types.version import LATEST_HANDSHAKE_VERSION, LATEST_MODERN_VERSION from pydantic import BaseModel, ValidationError @@ -364,6 +366,36 @@ def test_connection_check_capability_false_when_no_client_params_recorded(): (ClientCapabilities(experimental={"a": {}}), ClientCapabilities(experimental={"b": {}}), False), (ClientCapabilities(experimental={"a": {"x": 1}}), ClientCapabilities(experimental={"a": {"x": 2}}), False), (ClientCapabilities(experimental={"a": {}}), ClientCapabilities(experimental={"a": {}}), True), + ( + ClientCapabilities(elicitation=None), + ClientCapabilities(elicitation=ElicitationCapability()), + False, + ), + ( + ClientCapabilities(elicitation=ElicitationCapability(url=UrlElicitationCapability())), + ClientCapabilities(elicitation=ElicitationCapability(form=FormElicitationCapability())), + False, + ), + ( + ClientCapabilities(elicitation=ElicitationCapability(form=FormElicitationCapability())), + ClientCapabilities(elicitation=ElicitationCapability(url=UrlElicitationCapability())), + False, + ), + ( + ClientCapabilities(elicitation=ElicitationCapability(url=UrlElicitationCapability())), + ClientCapabilities(elicitation=ElicitationCapability(url=UrlElicitationCapability())), + True, + ), + ( + ClientCapabilities(elicitation=ElicitationCapability(form=FormElicitationCapability())), + ClientCapabilities(elicitation=ElicitationCapability(form=FormElicitationCapability())), + True, + ), + ( + ClientCapabilities(elicitation=ElicitationCapability(url=UrlElicitationCapability())), + ClientCapabilities(elicitation=ElicitationCapability()), + True, + ), ], ) def test_check_capability_per_field_branches(have: ClientCapabilities, want: ClientCapabilities, expected: bool):