Remove deprecated cursor parameter and ResourceReference (#1871)

This commit is contained in:
Marcelo Trylesinski 2026-01-15 21:33:21 +01:00 • committed by GitHub
commit 812a46ab97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 48 additions and 228 deletions

View file

@ -52,20 +52,25 @@ async with http_client:
The `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters have been removed from `StreamableHTTPTransport`. Configure these on the `httpx.AsyncClient` instead (see example above). The `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters have been removed from `StreamableHTTPTransport`. Configure these on the `httpx.AsyncClient` instead (see example above).
### `Content` type alias removed ### Removed type aliases and classes
The deprecated `Content` type alias has been removed. Use `ContentBlock` directly instead. The following deprecated type aliases and classes have been removed from `mcp.types`:
| Removed | Replacement |
|---------|-------------|
| `Content` | `ContentBlock` |
| `ResourceReference` | `ResourceTemplateReference` |
**Before (v1):** **Before (v1):**
```python ```python
from mcp.types import Content from mcp.types import Content, ResourceReference
``` ```
**After (v2):** **After (v2):**
```python ```python
from mcp.types import ContentBlock from mcp.types import ContentBlock, ResourceTemplateReference
``` ```
### `args` parameter removed from `ClientSessionGroup.call_tool()` ### `args` parameter removed from `ClientSessionGroup.call_tool()`
@ -84,6 +89,33 @@ result = await session_group.call_tool("my_tool", args={"key": "value"})
result = await session_group.call_tool("my_tool", arguments={"key": "value"}) result = await session_group.call_tool("my_tool", arguments={"key": "value"})
``` ```
### `cursor` parameter removed from `ClientSession` list methods
The deprecated `cursor` parameter has been removed from the following `ClientSession` methods:
- `list_resources()`
- `list_resource_templates()`
- `list_prompts()`
- `list_tools()`
Use `params=PaginatedRequestParams(cursor=...)` instead.
**Before (v1):**
```python
result = await session.list_resources(cursor="next_page_token")
result = await session.list_tools(cursor="next_page_token")
```
**After (v2):**
```python
from mcp.types import PaginatedRequestParams
result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token"))
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
```
## Deprecations ## Deprecations
<!-- Add deprecations below --> <!-- Add deprecations below -->

View file

@ -1,10 +1,9 @@
import logging import logging
from typing import Any, Protocol, overload from typing import Any, Protocol
import anyio.lowlevel import anyio.lowlevel
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from pydantic import AnyUrl, TypeAdapter from pydantic import AnyUrl, TypeAdapter
from typing_extensions import deprecated
import mcp.types as types import mcp.types as types
from mcp.client.experimental import ExperimentalClientFeatures from mcp.client.experimental import ExperimentalClientFeatures
@ -256,112 +255,48 @@ class ClientSession(
types.EmptyResult, types.EmptyResult,
) )
@overload async def list_resources(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListResourcesResult:
@deprecated("Use list_resources(params=PaginatedRequestParams(...)) instead")
async def list_resources(self, cursor: str | None) -> types.ListResourcesResult: ...
@overload
async def list_resources(self, *, params: types.PaginatedRequestParams | None) -> types.ListResourcesResult: ...
@overload
async def list_resources(self) -> types.ListResourcesResult: ...
async def list_resources(
self,
cursor: str | None = None,
*,
params: types.PaginatedRequestParams | None = None,
) -> types.ListResourcesResult:
"""Send a resources/list request. """Send a resources/list request.
Args: Args:
cursor: Simple cursor string for pagination (deprecated, use params instead)
params: Full pagination parameters including cursor and any future fields params: Full pagination parameters including cursor and any future fields
""" """
if params is not None and cursor is not None:
raise ValueError("Cannot specify both cursor and params")
if params is not None:
request_params = params
elif cursor is not None:
request_params = types.PaginatedRequestParams(cursor=cursor)
else:
request_params = None
return await self.send_request( return await self.send_request(
types.ClientRequest(types.ListResourcesRequest(params=request_params)), types.ClientRequest(types.ListResourcesRequest(params=params)),
types.ListResourcesResult, types.ListResourcesResult,
) )
@overload
@deprecated("Use list_resource_templates(params=PaginatedRequestParams(...)) instead")
async def list_resource_templates(self, cursor: str | None) -> types.ListResourceTemplatesResult: ...
@overload
async def list_resource_templates( async def list_resource_templates(
self, *, params: types.PaginatedRequestParams | None self, *, params: types.PaginatedRequestParams | None = None
) -> types.ListResourceTemplatesResult: ...
@overload
async def list_resource_templates(self) -> types.ListResourceTemplatesResult: ...
async def list_resource_templates(
self,
cursor: str | None = None,
*,
params: types.PaginatedRequestParams | None = None,
) -> types.ListResourceTemplatesResult: ) -> types.ListResourceTemplatesResult:
"""Send a resources/templates/list request. """Send a resources/templates/list request.
Args: Args:
cursor: Simple cursor string for pagination (deprecated, use params instead)
params: Full pagination parameters including cursor and any future fields params: Full pagination parameters including cursor and any future fields
""" """
if params is not None and cursor is not None:
raise ValueError("Cannot specify both cursor and params")
if params is not None:
request_params = params
elif cursor is not None:
request_params = types.PaginatedRequestParams(cursor=cursor)
else:
request_params = None
return await self.send_request( return await self.send_request(
types.ClientRequest(types.ListResourceTemplatesRequest(params=request_params)), types.ClientRequest(types.ListResourceTemplatesRequest(params=params)),
types.ListResourceTemplatesResult, types.ListResourceTemplatesResult,
) )
async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult: async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
"""Send a resources/read request.""" """Send a resources/read request."""
return await self.send_request( return await self.send_request(
types.ClientRequest( types.ClientRequest(types.ReadResourceRequest(params=types.ReadResourceRequestParams(uri=uri))),
types.ReadResourceRequest(
params=types.ReadResourceRequestParams(uri=uri),
)
),
types.ReadResourceResult, types.ReadResourceResult,
) )
async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
"""Send a resources/subscribe request.""" """Send a resources/subscribe request."""
return await self.send_request( # pragma: no cover return await self.send_request( # pragma: no cover
types.ClientRequest( types.ClientRequest(types.SubscribeRequest(params=types.SubscribeRequestParams(uri=uri))),
types.SubscribeRequest(
params=types.SubscribeRequestParams(uri=uri),
)
),
types.EmptyResult, types.EmptyResult,
) )
async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult: async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
"""Send a resources/unsubscribe request.""" """Send a resources/unsubscribe request."""
return await self.send_request( # pragma: no cover return await self.send_request( # pragma: no cover
types.ClientRequest( types.ClientRequest(types.UnsubscribeRequest(params=types.UnsubscribeRequestParams(uri=uri))),
types.UnsubscribeRequest(
params=types.UnsubscribeRequestParams(uri=uri),
)
),
types.EmptyResult, types.EmptyResult,
) )
@ -422,40 +357,14 @@ class ClientSession(
except SchemaError as e: # pragma: no cover except SchemaError as e: # pragma: no cover
raise RuntimeError(f"Invalid schema for tool {name}: {e}") # pragma: no cover raise RuntimeError(f"Invalid schema for tool {name}: {e}") # pragma: no cover
@overload async def list_prompts(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListPromptsResult:
@deprecated("Use list_prompts(params=PaginatedRequestParams(...)) instead")
async def list_prompts(self, cursor: str | None) -> types.ListPromptsResult: ...
@overload
async def list_prompts(self, *, params: types.PaginatedRequestParams | None) -> types.ListPromptsResult: ...
@overload
async def list_prompts(self) -> types.ListPromptsResult: ...
async def list_prompts(
self,
cursor: str | None = None,
*,
params: types.PaginatedRequestParams | None = None,
) -> types.ListPromptsResult:
"""Send a prompts/list request. """Send a prompts/list request.
Args: Args:
cursor: Simple cursor string for pagination (deprecated, use params instead)
params: Full pagination parameters including cursor and any future fields params: Full pagination parameters including cursor and any future fields
""" """
if params is not None and cursor is not None:
raise ValueError("Cannot specify both cursor and params")
if params is not None:
request_params = params
elif cursor is not None:
request_params = types.PaginatedRequestParams(cursor=cursor)
else:
request_params = None
return await self.send_request( return await self.send_request(
types.ClientRequest(types.ListPromptsRequest(params=request_params)), types.ClientRequest(types.ListPromptsRequest(params=params)),
types.ListPromptsResult, types.ListPromptsResult,
) )
@ -494,40 +403,15 @@ class ClientSession(
types.CompleteResult, types.CompleteResult,
) )
@overload async def list_tools(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListToolsResult:
@deprecated("Use list_tools(params=PaginatedRequestParams(...)) instead")
async def list_tools(self, cursor: str | None) -> types.ListToolsResult: ...
@overload
async def list_tools(self, *, params: types.PaginatedRequestParams | None) -> types.ListToolsResult: ...
@overload
async def list_tools(self) -> types.ListToolsResult: ...
async def list_tools(
self,
cursor: str | None = None,
*,
params: types.PaginatedRequestParams | None = None,
) -> types.ListToolsResult:
"""Send a tools/list request. """Send a tools/list request.
Args: Args:
cursor: Simple cursor string for pagination (deprecated, use params instead) cursor: Simple cursor string for pagination (deprecated, use params instead)
params: Full pagination parameters including cursor and any future fields params: Full pagination parameters including cursor and any future fields
""" """
if params is not None and cursor is not None:
raise ValueError("Cannot specify both cursor and params")
if params is not None:
request_params = params
elif cursor is not None:
request_params = types.PaginatedRequestParams(cursor=cursor)
else:
request_params = None
result = await self.send_request( result = await self.send_request(
types.ClientRequest(types.ListToolsRequest(params=request_params)), types.ClientRequest(types.ListToolsRequest(params=params)),
types.ListToolsResult, types.ListToolsResult,
) )

View file

@ -6,7 +6,6 @@ from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
from pydantic.networks import AnyUrl, UrlConstraints from pydantic.networks import AnyUrl, UrlConstraints
from typing_extensions import deprecated
LATEST_PROTOCOL_VERSION = "2025-11-25" LATEST_PROTOCOL_VERSION = "2025-11-25"
@ -1587,11 +1586,6 @@ class ResourceTemplateReference(BaseModel):
model_config = ConfigDict(extra="allow") model_config = ConfigDict(extra="allow")
@deprecated("`ResourceReference` is deprecated, you should use `ResourceTemplateReference`.")
class ResourceReference(ResourceTemplateReference):
pass
class PromptReference(BaseModel): class PromptReference(BaseModel):
"""Identifies a prompt.""" """Identifies a prompt."""

View file

@ -46,65 +46,6 @@ async def full_featured_server():
return server return server
@pytest.mark.parametrize(
"method_name,request_method",
[
("list_tools", "tools/list"),
("list_resources", "resources/list"),
("list_prompts", "prompts/list"),
("list_resource_templates", "resources/templates/list"),
],
)
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
async def test_list_methods_cursor_parameter(
stream_spy: Callable[[], StreamSpyCollection],
full_featured_server: FastMCP,
method_name: str,
request_method: str,
):
"""Test that the cursor parameter is accepted and correctly passed to the server.
Covers: list_tools, list_resources, list_prompts, list_resource_templates
See: https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/pagination#request-format
"""
async with create_session(full_featured_server._mcp_server) as client_session:
spies = stream_spy()
# Test without cursor parameter (omitted)
method = getattr(client_session, method_name)
_ = await method()
requests = spies.get_client_requests(method=request_method)
assert len(requests) == 1
assert requests[0].params is None
spies.clear()
# Test with cursor=None
_ = await method(cursor=None)
requests = spies.get_client_requests(method=request_method)
assert len(requests) == 1
assert requests[0].params is None
spies.clear()
# Test with cursor as string
_ = await method(cursor="some_cursor_value")
requests = spies.get_client_requests(method=request_method)
assert len(requests) == 1
assert requests[0].params is not None
assert requests[0].params["cursor"] == "some_cursor_value"
spies.clear()
# Test with empty string cursor
_ = await method(cursor="")
requests = spies.get_client_requests(method=request_method)
assert len(requests) == 1
assert requests[0].params is not None
assert requests[0].params["cursor"] == ""
@pytest.mark.parametrize( @pytest.mark.parametrize(
"method_name,request_method", "method_name,request_method",
[ [
@ -164,37 +105,6 @@ async def test_list_methods_params_parameter(
assert requests[0].params["cursor"] == "some_cursor_value" assert requests[0].params["cursor"] == "some_cursor_value"
@pytest.mark.parametrize(
"method_name",
[
"list_tools",
"list_resources",
"list_prompts",
"list_resource_templates",
],
)
async def test_list_methods_raises_error_when_both_cursor_and_params_provided(
full_featured_server: FastMCP,
method_name: str,
):
"""Test that providing both cursor and params raises ValueError.
Covers: list_tools, list_resources, list_prompts, list_resource_templates
When both cursor and params are provided, a ValueError should be raised
to prevent ambiguity.
"""
async with create_session(full_featured_server._mcp_server) as client_session:
method = getattr(client_session, method_name)
# Call with both cursor and params - should raise ValueError
with pytest.raises(ValueError, match="Cannot specify both cursor and params"):
await method(
cursor="old_cursor",
params=types.PaginatedRequestParams(cursor="new_cursor"),
)
async def test_list_tools_with_strict_server_validation(): async def test_list_tools_with_strict_server_validation():
"""Test that list_tools works with strict servers require a params field, """Test that list_tools works with strict servers require a params field,
even if it is empty. even if it is empty.