Skip to content

Commit cf09d87

Browse files
authored
Merge branch 'main' into main
2 parents bf33363 + de89457 commit cf09d87

File tree

11 files changed

+486
-185
lines changed

11 files changed

+486
-185
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,7 +1840,7 @@ import asyncio
18401840

18411841
from mcp.client.session import ClientSession
18421842
from mcp.client.stdio import StdioServerParameters, stdio_client
1843-
from mcp.types import Resource
1843+
from mcp.types import PaginatedRequestParams, Resource
18441844

18451845

18461846
async def list_all_resources() -> None:
@@ -1857,7 +1857,7 @@ async def list_all_resources() -> None:
18571857

18581858
while True:
18591859
# Fetch a page of resources
1860-
result = await session.list_resources(cursor=cursor)
1860+
result = await session.list_resources(params=PaginatedRequestParams(cursor=cursor))
18611861
all_resources.extend(result.resources)
18621862

18631863
print(f"Fetched {len(result.resources)} resources")

examples/snippets/clients/pagination_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from mcp.client.session import ClientSession
88
from mcp.client.stdio import StdioServerParameters, stdio_client
9-
from mcp.types import Resource
9+
from mcp.types import PaginatedRequestParams, Resource
1010

1111

1212
async def list_all_resources() -> None:
@@ -23,7 +23,7 @@ async def list_all_resources() -> None:
2323

2424
while True:
2525
# Fetch a page of resources
26-
result = await session.list_resources(cursor=cursor)
26+
result = await session.list_resources(params=PaginatedRequestParams(cursor=cursor))
2727
all_resources.extend(result.resources)
2828

2929
print(f"Fetched {len(result.resources)} resources")

src/mcp/client/session.py

Lines changed: 136 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import logging
22
from datetime import timedelta
3-
from typing import Any, Protocol
3+
from typing import Any, Protocol, overload
44

55
import anyio.lowlevel
66
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
77
from jsonschema import SchemaError, ValidationError, validate
88
from pydantic import AnyUrl, TypeAdapter
9+
from typing_extensions import deprecated
910

1011
import mcp.types as types
1112
from mcp.shared.context import RequestContext
@@ -212,25 +213,79 @@ async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResul
212213
types.EmptyResult,
213214
)
214215

215-
async def list_resources(self, cursor: str | None = None) -> types.ListResourcesResult:
216-
"""Send a resources/list request."""
216+
@overload
217+
@deprecated("Use list_resources(params=PaginatedRequestParams(...)) instead")
218+
async def list_resources(self, cursor: str | None) -> types.ListResourcesResult: ...
219+
220+
@overload
221+
async def list_resources(self, *, params: types.PaginatedRequestParams | None) -> types.ListResourcesResult: ...
222+
223+
@overload
224+
async def list_resources(self) -> types.ListResourcesResult: ...
225+
226+
async def list_resources(
227+
self,
228+
cursor: str | None = None,
229+
*,
230+
params: types.PaginatedRequestParams | None = None,
231+
) -> types.ListResourcesResult:
232+
"""Send a resources/list request.
233+
234+
Args:
235+
cursor: Simple cursor string for pagination (deprecated, use params instead)
236+
params: Full pagination parameters including cursor and any future fields
237+
"""
238+
if params is not None and cursor is not None:
239+
raise ValueError("Cannot specify both cursor and params")
240+
241+
if params is not None:
242+
request_params = params
243+
elif cursor is not None:
244+
request_params = types.PaginatedRequestParams(cursor=cursor)
245+
else:
246+
request_params = None
247+
217248
return await self.send_request(
218-
types.ClientRequest(
219-
types.ListResourcesRequest(
220-
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
221-
)
222-
),
249+
types.ClientRequest(types.ListResourcesRequest(params=request_params)),
223250
types.ListResourcesResult,
224251
)
225252

226-
async def list_resource_templates(self, cursor: str | None = None) -> types.ListResourceTemplatesResult:
227-
"""Send a resources/templates/list request."""
253+
@overload
254+
@deprecated("Use list_resource_templates(params=PaginatedRequestParams(...)) instead")
255+
async def list_resource_templates(self, cursor: str | None) -> types.ListResourceTemplatesResult: ...
256+
257+
@overload
258+
async def list_resource_templates(
259+
self, *, params: types.PaginatedRequestParams | None
260+
) -> types.ListResourceTemplatesResult: ...
261+
262+
@overload
263+
async def list_resource_templates(self) -> types.ListResourceTemplatesResult: ...
264+
265+
async def list_resource_templates(
266+
self,
267+
cursor: str | None = None,
268+
*,
269+
params: types.PaginatedRequestParams | None = None,
270+
) -> types.ListResourceTemplatesResult:
271+
"""Send a resources/templates/list request.
272+
273+
Args:
274+
cursor: Simple cursor string for pagination (deprecated, use params instead)
275+
params: Full pagination parameters including cursor and any future fields
276+
"""
277+
if params is not None and cursor is not None:
278+
raise ValueError("Cannot specify both cursor and params")
279+
280+
if params is not None:
281+
request_params = params
282+
elif cursor is not None:
283+
request_params = types.PaginatedRequestParams(cursor=cursor)
284+
else:
285+
request_params = None
286+
228287
return await self.send_request(
229-
types.ClientRequest(
230-
types.ListResourceTemplatesRequest(
231-
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
232-
)
233-
),
288+
types.ClientRequest(types.ListResourceTemplatesRequest(params=request_params)),
234289
types.ListResourceTemplatesResult,
235290
)
236291

@@ -317,14 +372,40 @@ async def _validate_tool_result(self, name: str, result: types.CallToolResult) -
317372
except SchemaError as e:
318373
raise RuntimeError(f"Invalid schema for tool {name}: {e}")
319374

320-
async def list_prompts(self, cursor: str | None = None) -> types.ListPromptsResult:
321-
"""Send a prompts/list request."""
375+
@overload
376+
@deprecated("Use list_prompts(params=PaginatedRequestParams(...)) instead")
377+
async def list_prompts(self, cursor: str | None) -> types.ListPromptsResult: ...
378+
379+
@overload
380+
async def list_prompts(self, *, params: types.PaginatedRequestParams | None) -> types.ListPromptsResult: ...
381+
382+
@overload
383+
async def list_prompts(self) -> types.ListPromptsResult: ...
384+
385+
async def list_prompts(
386+
self,
387+
cursor: str | None = None,
388+
*,
389+
params: types.PaginatedRequestParams | None = None,
390+
) -> types.ListPromptsResult:
391+
"""Send a prompts/list request.
392+
393+
Args:
394+
cursor: Simple cursor string for pagination (deprecated, use params instead)
395+
params: Full pagination parameters including cursor and any future fields
396+
"""
397+
if params is not None and cursor is not None:
398+
raise ValueError("Cannot specify both cursor and params")
399+
400+
if params is not None:
401+
request_params = params
402+
elif cursor is not None:
403+
request_params = types.PaginatedRequestParams(cursor=cursor)
404+
else:
405+
request_params = None
406+
322407
return await self.send_request(
323-
types.ClientRequest(
324-
types.ListPromptsRequest(
325-
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
326-
)
327-
),
408+
types.ClientRequest(types.ListPromptsRequest(params=request_params)),
328409
types.ListPromptsResult,
329410
)
330411

@@ -363,14 +444,40 @@ async def complete(
363444
types.CompleteResult,
364445
)
365446

366-
async def list_tools(self, cursor: str | None = None) -> types.ListToolsResult:
367-
"""Send a tools/list request."""
447+
@overload
448+
@deprecated("Use list_tools(params=PaginatedRequestParams(...)) instead")
449+
async def list_tools(self, cursor: str | None) -> types.ListToolsResult: ...
450+
451+
@overload
452+
async def list_tools(self, *, params: types.PaginatedRequestParams | None) -> types.ListToolsResult: ...
453+
454+
@overload
455+
async def list_tools(self) -> types.ListToolsResult: ...
456+
457+
async def list_tools(
458+
self,
459+
cursor: str | None = None,
460+
*,
461+
params: types.PaginatedRequestParams | None = None,
462+
) -> types.ListToolsResult:
463+
"""Send a tools/list request.
464+
465+
Args:
466+
cursor: Simple cursor string for pagination (deprecated, use params instead)
467+
params: Full pagination parameters including cursor and any future fields
468+
"""
469+
if params is not None and cursor is not None:
470+
raise ValueError("Cannot specify both cursor and params")
471+
472+
if params is not None:
473+
request_params = params
474+
elif cursor is not None:
475+
request_params = types.PaginatedRequestParams(cursor=cursor)
476+
else:
477+
request_params = None
478+
368479
result = await self.send_request(
369-
types.ClientRequest(
370-
types.ListToolsRequest(
371-
params=types.PaginatedRequestParams(cursor=cursor) if cursor is not None else None,
372-
)
373-
),
480+
types.ClientRequest(types.ListToolsRequest(params=request_params)),
374481
types.ListToolsResult,
375482
)
376483

src/mcp/server/fastmcp/resources/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
field_validator,
1414
)
1515

16-
from mcp.types import Icon
16+
from mcp.types import Annotations, Icon
1717

1818

1919
class Resource(BaseModel, abc.ABC):
@@ -31,6 +31,7 @@ class Resource(BaseModel, abc.ABC):
3131
pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
3232
)
3333
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this resource")
34+
annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource")
3435

3536
@field_validator("name", mode="before")
3637
@classmethod

src/mcp/server/fastmcp/resources/resource_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from mcp.server.fastmcp.resources.base import Resource
1111
from mcp.server.fastmcp.resources.templates import ResourceTemplate
1212
from mcp.server.fastmcp.utilities.logging import get_logger
13-
from mcp.types import Icon
13+
from mcp.types import Annotations, Icon
1414

1515
if TYPE_CHECKING:
1616
from mcp.server.fastmcp.server import Context
@@ -63,6 +63,7 @@ def add_template(
6363
description: str | None = None,
6464
mime_type: str | None = None,
6565
icons: list[Icon] | None = None,
66+
annotations: Annotations | None = None,
6667
) -> ResourceTemplate:
6768
"""Add a template from a function."""
6869
template = ResourceTemplate.from_function(
@@ -73,6 +74,7 @@ def add_template(
7374
description=description,
7475
mime_type=mime_type,
7576
icons=icons,
77+
annotations=annotations,
7678
)
7779
self._templates[template.uri_template] = template
7880
return template

src/mcp/server/fastmcp/resources/templates.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from mcp.server.fastmcp.resources.types import FunctionResource, Resource
1313
from mcp.server.fastmcp.utilities.context_injection import find_context_parameter, inject_context
1414
from mcp.server.fastmcp.utilities.func_metadata import func_metadata
15-
from mcp.types import Icon
15+
from mcp.types import Annotations, Icon
1616

1717
if TYPE_CHECKING:
1818
from mcp.server.fastmcp.server import Context
@@ -29,6 +29,7 @@ class ResourceTemplate(BaseModel):
2929
description: str | None = Field(description="Description of what the resource does")
3030
mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
3131
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for the resource template")
32+
annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource template")
3233
fn: Callable[..., Any] = Field(exclude=True)
3334
parameters: dict[str, Any] = Field(description="JSON schema for function parameters")
3435
context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
@@ -43,6 +44,7 @@ def from_function(
4344
description: str | None = None,
4445
mime_type: str | None = None,
4546
icons: list[Icon] | None = None,
47+
annotations: Annotations | None = None,
4648
context_kwarg: str | None = None,
4749
) -> ResourceTemplate:
4850
"""Create a template from a function."""
@@ -71,6 +73,7 @@ def from_function(
7173
description=description or fn.__doc__ or "",
7274
mime_type=mime_type or "text/plain",
7375
icons=icons,
76+
annotations=annotations,
7477
fn=fn,
7578
parameters=parameters,
7679
context_kwarg=context_kwarg,
@@ -108,6 +111,7 @@ async def create_resource(
108111
description=self.description,
109112
mime_type=self.mime_type,
110113
icons=self.icons,
114+
annotations=self.annotations,
111115
fn=lambda: result, # Capture result in closure
112116
)
113117
except Exception as e:

src/mcp/server/fastmcp/resources/types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from pydantic import AnyUrl, Field, ValidationInfo, validate_call
1515

1616
from mcp.server.fastmcp.resources.base import Resource
17-
from mcp.types import Icon
17+
from mcp.types import Annotations, Icon
1818

1919

2020
class TextResource(Resource):
@@ -82,6 +82,7 @@ def from_function(
8282
description: str | None = None,
8383
mime_type: str | None = None,
8484
icons: list[Icon] | None = None,
85+
annotations: Annotations | None = None,
8586
) -> "FunctionResource":
8687
"""Create a FunctionResource from a function."""
8788
func_name = name or fn.__name__
@@ -99,6 +100,7 @@ def from_function(
99100
mime_type=mime_type or "text/plain",
100101
fn=fn,
101102
icons=icons,
103+
annotations=annotations,
102104
)
103105

104106

src/mcp/server/fastmcp/server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
4444
from mcp.server.transport_security import TransportSecuritySettings
4545
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
46-
from mcp.types import AnyFunction, ContentBlock, GetPromptResult, Icon, ToolAnnotations
46+
from mcp.types import Annotations, AnyFunction, ContentBlock, GetPromptResult, Icon, ToolAnnotations
4747
from mcp.types import Prompt as MCPPrompt
4848
from mcp.types import PromptArgument as MCPPromptArgument
4949
from mcp.types import Resource as MCPResource
@@ -322,6 +322,7 @@ async def list_resources(self) -> list[MCPResource]:
322322
description=resource.description,
323323
mimeType=resource.mime_type,
324324
icons=resource.icons,
325+
annotations=resource.annotations,
325326
)
326327
for resource in resources
327328
]
@@ -336,6 +337,7 @@ async def list_resource_templates(self) -> list[MCPResourceTemplate]:
336337
description=template.description,
337338
mimeType=template.mime_type,
338339
icons=template.icons,
340+
annotations=template.annotations,
339341
)
340342
for template in templates
341343
]
@@ -497,6 +499,7 @@ def resource(
497499
description: str | None = None,
498500
mime_type: str | None = None,
499501
icons: list[Icon] | None = None,
502+
annotations: Annotations | None = None,
500503
) -> Callable[[AnyFunction], AnyFunction]:
501504
"""Decorator to register a function as a resource.
502505
@@ -572,6 +575,7 @@ def decorator(fn: AnyFunction) -> AnyFunction:
572575
description=description,
573576
mime_type=mime_type,
574577
icons=icons,
578+
annotations=annotations,
575579
)
576580
else:
577581
# Register as regular resource
@@ -583,6 +587,7 @@ def decorator(fn: AnyFunction) -> AnyFunction:
583587
description=description,
584588
mime_type=mime_type,
585589
icons=icons,
590+
annotations=annotations,
586591
)
587592
self.add_resource(resource)
588593
return fn

0 commit comments

Comments
 (0)