Remove deprecated cursor parameter and ResourceReference (#1871)
This commit is contained in:
parent
72a8631f0a
commit
812a46ab97
4 changed files with 48 additions and 228 deletions
|
|
@ -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).
|
||||
|
||||
### `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):**
|
||||
|
||||
```python
|
||||
from mcp.types import Content
|
||||
from mcp.types import Content, ResourceReference
|
||||
```
|
||||
|
||||
**After (v2):**
|
||||
|
||||
```python
|
||||
from mcp.types import ContentBlock
|
||||
from mcp.types import ContentBlock, ResourceTemplateReference
|
||||
```
|
||||
|
||||
### `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"})
|
||||
```
|
||||
|
||||
### `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
|
||||
|
||||
<!-- Add deprecations below -->
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import logging
|
||||
from typing import Any, Protocol, overload
|
||||
from typing import Any, Protocol
|
||||
|
||||
import anyio.lowlevel
|
||||
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
||||
from pydantic import AnyUrl, TypeAdapter
|
||||
from typing_extensions import deprecated
|
||||
|
||||
import mcp.types as types
|
||||
from mcp.client.experimental import ExperimentalClientFeatures
|
||||
|
|
@ -256,112 +255,48 @@ class ClientSession(
|
|||
types.EmptyResult,
|
||||
)
|
||||
|
||||
@overload
|
||||
@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:
|
||||
async def list_resources(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListResourcesResult:
|
||||
"""Send a resources/list request.
|
||||
|
||||
Args:
|
||||
cursor: Simple cursor string for pagination (deprecated, use params instead)
|
||||
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(
|
||||
types.ClientRequest(types.ListResourcesRequest(params=request_params)),
|
||||
types.ClientRequest(types.ListResourcesRequest(params=params)),
|
||||
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(
|
||||
self, *, params: types.PaginatedRequestParams | 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,
|
||||
self, *, params: types.PaginatedRequestParams | None = None
|
||||
) -> types.ListResourceTemplatesResult:
|
||||
"""Send a resources/templates/list request.
|
||||
|
||||
Args:
|
||||
cursor: Simple cursor string for pagination (deprecated, use params instead)
|
||||
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(
|
||||
types.ClientRequest(types.ListResourceTemplatesRequest(params=request_params)),
|
||||
types.ClientRequest(types.ListResourceTemplatesRequest(params=params)),
|
||||
types.ListResourceTemplatesResult,
|
||||
)
|
||||
|
||||
async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
|
||||
"""Send a resources/read request."""
|
||||
return await self.send_request(
|
||||
types.ClientRequest(
|
||||
types.ReadResourceRequest(
|
||||
params=types.ReadResourceRequestParams(uri=uri),
|
||||
)
|
||||
),
|
||||
types.ClientRequest(types.ReadResourceRequest(params=types.ReadResourceRequestParams(uri=uri))),
|
||||
types.ReadResourceResult,
|
||||
)
|
||||
|
||||
async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
|
||||
"""Send a resources/subscribe request."""
|
||||
return await self.send_request( # pragma: no cover
|
||||
types.ClientRequest(
|
||||
types.SubscribeRequest(
|
||||
params=types.SubscribeRequestParams(uri=uri),
|
||||
)
|
||||
),
|
||||
types.ClientRequest(types.SubscribeRequest(params=types.SubscribeRequestParams(uri=uri))),
|
||||
types.EmptyResult,
|
||||
)
|
||||
|
||||
async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
|
||||
"""Send a resources/unsubscribe request."""
|
||||
return await self.send_request( # pragma: no cover
|
||||
types.ClientRequest(
|
||||
types.UnsubscribeRequest(
|
||||
params=types.UnsubscribeRequestParams(uri=uri),
|
||||
)
|
||||
),
|
||||
types.ClientRequest(types.UnsubscribeRequest(params=types.UnsubscribeRequestParams(uri=uri))),
|
||||
types.EmptyResult,
|
||||
)
|
||||
|
||||
|
|
@ -422,40 +357,14 @@ class ClientSession(
|
|||
except SchemaError as e: # pragma: no cover
|
||||
raise RuntimeError(f"Invalid schema for tool {name}: {e}") # pragma: no cover
|
||||
|
||||
@overload
|
||||
@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:
|
||||
async def list_prompts(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListPromptsResult:
|
||||
"""Send a prompts/list request.
|
||||
|
||||
Args:
|
||||
cursor: Simple cursor string for pagination (deprecated, use params instead)
|
||||
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(
|
||||
types.ClientRequest(types.ListPromptsRequest(params=request_params)),
|
||||
types.ClientRequest(types.ListPromptsRequest(params=params)),
|
||||
types.ListPromptsResult,
|
||||
)
|
||||
|
||||
|
|
@ -494,40 +403,15 @@ class ClientSession(
|
|||
types.CompleteResult,
|
||||
)
|
||||
|
||||
@overload
|
||||
@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:
|
||||
async def list_tools(self, *, params: types.PaginatedRequestParams | None = None) -> types.ListToolsResult:
|
||||
"""Send a tools/list request.
|
||||
|
||||
Args:
|
||||
cursor: Simple cursor string for pagination (deprecated, use params instead)
|
||||
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(
|
||||
types.ClientRequest(types.ListToolsRequest(params=request_params)),
|
||||
types.ClientRequest(types.ListToolsRequest(params=params)),
|
||||
types.ListToolsResult,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
|
|||
|
||||
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
|
||||
from pydantic.networks import AnyUrl, UrlConstraints
|
||||
from typing_extensions import deprecated
|
||||
|
||||
LATEST_PROTOCOL_VERSION = "2025-11-25"
|
||||
|
||||
|
|
@ -1587,11 +1586,6 @@ class ResourceTemplateReference(BaseModel):
|
|||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
@deprecated("`ResourceReference` is deprecated, you should use `ResourceTemplateReference`.")
|
||||
class ResourceReference(ResourceTemplateReference):
|
||||
pass
|
||||
|
||||
|
||||
class PromptReference(BaseModel):
|
||||
"""Identifies a prompt."""
|
||||
|
||||
|
|
|
|||
|
|
@ -46,65 +46,6 @@ async def full_featured_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(
|
||||
"method_name,request_method",
|
||||
[
|
||||
|
|
@ -164,37 +105,6 @@ async def test_list_methods_params_parameter(
|
|||
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():
|
||||
"""Test that list_tools works with strict servers require a params field,
|
||||
even if it is empty.
|
||||
|
|
|
|||
Loading…
Reference in a new issue