@@ -1759,6 +1759,75 @@ async def test_a_boolean_inbound_ttl_is_not_clamped_only_coerced_by_validation(w
17591759 assert result .ttl_ms == int (wire_ttl )
17601760
17611761
1762+ _LEGACY_HINTED_RESULTS : list [tuple [str , dict [str , Any ]]] = [
1763+ ("list_tools" , {"tools" : []}),
1764+ ("list_prompts" , {"prompts" : []}),
1765+ ("list_resources" , {"resources" : []}),
1766+ ("list_resource_templates" , {"resourceTemplates" : []}),
1767+ ("read_resource" , {"contents" : []}),
1768+ ]
1769+
1770+ _LEGACY_TAGGED_RESULTS : list [tuple [str , dict [str , Any ]]] = [
1771+ ("call_tool" , {"content" : []}),
1772+ ("get_prompt" , {"messages" : []}),
1773+ ]
1774+
1775+
1776+ def _legacy_init (version : str ) -> dict [str , Any ]:
1777+ return InitializeResult (
1778+ protocol_version = version ,
1779+ capabilities = ServerCapabilities (),
1780+ server_info = Implementation (name = "mock-server" , version = "0.1.0" ),
1781+ ).model_dump (by_alias = True , mode = "json" , exclude_none = True )
1782+
1783+
1784+ async def _call_legacy (session : ClientSession , verb : str ) -> Any :
1785+ if verb == "read_resource" :
1786+ return await session .read_resource ("mem://x" )
1787+ if verb == "call_tool" :
1788+ return await session .call_tool ("t" , {})
1789+ if verb == "get_prompt" :
1790+ return await session .get_prompt ("p" )
1791+ return await getattr (session , verb )()
1792+
1793+
1794+ @pytest .mark .anyio
1795+ @pytest .mark .parametrize ("version" , HANDSHAKE_PROTOCOL_VERSIONS )
1796+ @pytest .mark .parametrize (("verb" , "body" ), _LEGACY_HINTED_RESULTS )
1797+ async def test_cache_hints_from_a_legacy_server_never_reach_the_result (
1798+ version : str , verb : str , body : dict [str , Any ]
1799+ ) -> None :
1800+ """SDK-defined: on any pre-2026 session the caching fields are outside the negotiated
1801+ schema, so whatever a server puts in them - even values the 2026-07-28 enum
1802+ rejects - is dropped and the model shows its conservative defaults."""
1803+ dispatcher = _ScriptedDispatcher (_legacy_init (version ), {** body , "ttlMs" : - 1 , "cacheScope" : "session" })
1804+ with anyio .fail_after (5 ):
1805+ async with ClientSession (dispatcher = dispatcher ) as session :
1806+ await session .initialize ()
1807+ result = await _call_legacy (session , verb )
1808+ assert (result .ttl_ms , result .cache_scope ) == (0 , "private" )
1809+ assert not {"ttl_ms" , "cache_scope" } & result .model_fields_set
1810+
1811+
1812+ @pytest .mark .anyio
1813+ @pytest .mark .parametrize (("verb" , "body" ), _LEGACY_TAGGED_RESULTS )
1814+ async def test_a_2026_result_type_tag_from_a_legacy_server_never_reaches_the_result (
1815+ verb : str , body : dict [str , Any ]
1816+ ) -> None :
1817+ """SDK-defined: `resultType` is 2026-07-28 vocabulary that also feeds result-union
1818+ routing, so a tag on a pre-2026 wire (even one no union arm claims) is dropped and
1819+ the plain result is returned rather than mis-routing or failing."""
1820+ # `call_tool` re-lists tools to validate structured content; that answer trails harmlessly for `get_prompt`.
1821+ dispatcher = _ScriptedDispatcher (
1822+ _legacy_init (LATEST_HANDSHAKE_VERSION ), {** body , "resultType" : "task" }, {"tools" : []}
1823+ )
1824+ with anyio .fail_after (5 ):
1825+ async with ClientSession (dispatcher = dispatcher ) as session :
1826+ await session .initialize ()
1827+ result = await _call_legacy (session , verb )
1828+ assert result .result_type == "complete"
1829+
1830+
17621831@pytest .mark .anyio
17631832async def test_session_call_tool_returns_input_required_result_when_opted_in () -> None :
17641833 """`ClientSession.call_tool(..., allow_input_required=True)` surfaces the
0 commit comments