From 8c872a7d2a962746e4db06f49f10f485eb2bd59c Mon Sep 17 00:00:00 2001 From: bzsurbhi Date: Mon, 19 May 2025 15:38:35 -0700 Subject: [PATCH 1/2] fix: add status field in Resource class and Resource as a return type in CallToolResult --- src/mcp/__init__.py | 2 ++ src/mcp/server/fastmcp/resources/base.py | 6 ++++++ .../fastmcp/resources/resource_manager.py | 16 ++++++++++++++++ src/mcp/server/fastmcp/server.py | 6 ++++-- src/mcp/server/lowlevel/server.py | 5 ++++- src/mcp/types.py | 19 ++++++++++++++++++- tests/issues/test_152_resource_mime_type.py | 6 +++++- tests/shared/test_memory.py | 1 + 8 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/mcp/__init__.py b/src/mcp/__init__.py index 0d3c372ce..1462e79f3 100644 --- a/src/mcp/__init__.py +++ b/src/mcp/__init__.py @@ -38,6 +38,7 @@ ReadResourceResult, Resource, ResourcesCapability, + ResourceStatus, ResourceUpdatedNotification, RootsCapability, SamplingMessage, @@ -90,6 +91,7 @@ "ReadResourceRequest", "ReadResourceResult", "ResourcesCapability", + "ResourceStatus", "ResourceUpdatedNotification", "Resource", "RootsCapability", diff --git a/src/mcp/server/fastmcp/resources/base.py b/src/mcp/server/fastmcp/resources/base.py index b2050e7f8..5c9098244 100644 --- a/src/mcp/server/fastmcp/resources/base.py +++ b/src/mcp/server/fastmcp/resources/base.py @@ -13,6 +13,8 @@ field_validator, ) +from mcp.types import ResourceStatus as MCPResourceStatus + class Resource(BaseModel, abc.ABC): """Base class for all resources.""" @@ -31,6 +33,10 @@ class Resource(BaseModel, abc.ABC): description="MIME type of the resource content", pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$", ) + status: MCPResourceStatus = Field( + default=MCPResourceStatus.PENDING, + description="Status of the resource", + ) @field_validator("name", mode="before") @classmethod diff --git a/src/mcp/server/fastmcp/resources/resource_manager.py b/src/mcp/server/fastmcp/resources/resource_manager.py index d27e6ac12..09cc69df5 100644 --- a/src/mcp/server/fastmcp/resources/resource_manager.py +++ b/src/mcp/server/fastmcp/resources/resource_manager.py @@ -89,6 +89,22 @@ def list_resources(self) -> list[Resource]: logger.debug("Listing resources", extra={"count": len(self._resources)}) return list(self._resources.values()) + def update_resource(self, resource: Resource) -> Resource: + """Update an existing resource.""" + logger.debug( + "Updating resource", + extra={ + "uri": resource.uri, + "type": type(resource).__name__, + "status": resource.status, + "resource_name": resource.name, + }, + ) + if str(resource.uri) not in self._resources: + raise ValueError(f"Resource not found: {resource.uri}") + self._resources[str(resource.uri)] = resource + return resource + def list_templates(self) -> list[ResourceTemplate]: """List all registered templates.""" logger.debug("Listing templates", extra={"count": len(self._templates)}) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index c31f29d4c..174ac70a7 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -269,7 +269,7 @@ def get_context(self) -> Context[ServerSession, object]: async def call_tool( self, name: str, arguments: dict[str, Any] - ) -> Sequence[TextContent | ImageContent | EmbeddedResource]: + ) -> Sequence[TextContent | ImageContent | EmbeddedResource | MCPResource]: """Call a tool by name with arguments.""" context = self.get_context() result = await self._tool_manager.call_tool(name, arguments, context=context) @@ -282,10 +282,12 @@ async def list_resources(self) -> list[MCPResource]: resources = self._resource_manager.list_resources() return [ MCPResource( + type="resource", uri=resource.uri, name=resource.name or "", description=resource.description, mimeType=resource.mime_type, + status=resource.status, ) for resource in resources ] @@ -869,7 +871,7 @@ async def get_prompt( def _convert_to_content( result: Any, -) -> Sequence[TextContent | ImageContent | EmbeddedResource]: +) -> Sequence[TextContent | ImageContent | EmbeddedResource | MCPResource]: """Convert a result to a sequence of content objects.""" if result is None: return [] diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 4b97b33da..65c2fdbf5 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -399,7 +399,10 @@ def decorator( ..., Awaitable[ Iterable[ - types.TextContent | types.ImageContent | types.EmbeddedResource + types.TextContent + | types.ImageContent + | types.EmbeddedResource + | types.Resource ] ], ], diff --git a/src/mcp/types.py b/src/mcp/types.py index 6ab7fba5c..f0490b72c 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1,4 +1,5 @@ from collections.abc import Callable +from enum import Enum from typing import ( Annotated, Any, @@ -368,15 +369,31 @@ class Annotations(BaseModel): model_config = ConfigDict(extra="allow") +class ResourceStatus(str, Enum): + """The status of a resource.""" + + READY = "ready" + """Resource is ready to be read.""" + PENDING = "pending" + """Resource is being created or processed.""" + ERROR = "error" + """Resource has an error state.""" + DELETED = "deleted" + """Resource has been deleted.""" + + class Resource(BaseModel): """A known resource that the server is capable of reading.""" + type: Literal["resource"] uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] """The URI of this resource.""" name: str """A human-readable name for this resource.""" description: str | None = None """A description of what this resource represents.""" + status: ResourceStatus | None = None + """The status of this resource.""" mimeType: str | None = None """The MIME type of this resource, if known.""" size: int | None = None @@ -791,7 +808,7 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): class CallToolResult(Result): """The server's response to a tool call.""" - content: list[TextContent | ImageContent | EmbeddedResource] + content: list[TextContent | ImageContent | EmbeddedResource | Resource] isError: bool = False diff --git a/tests/issues/test_152_resource_mime_type.py b/tests/issues/test_152_resource_mime_type.py index 1143195e5..3da83a146 100644 --- a/tests/issues/test_152_resource_mime_type.py +++ b/tests/issues/test_152_resource_mime_type.py @@ -83,12 +83,16 @@ async def test_lowlevel_resource_mime_type(): # Create test resources with specific mime types test_resources = [ types.Resource( - uri=AnyUrl("test://image"), name="test image", mimeType="image/png" + uri=AnyUrl("test://image"), + name="test image", + mimeType="image/png", + type="resource", ), types.Resource( uri=AnyUrl("test://image_bytes"), name="test image bytes", mimeType="image/png", + type="resource", ), ] diff --git a/tests/shared/test_memory.py b/tests/shared/test_memory.py index a0c32f556..22f3dfe63 100644 --- a/tests/shared/test_memory.py +++ b/tests/shared/test_memory.py @@ -24,6 +24,7 @@ async def handle_list_resources(): uri=AnyUrl("memory://test"), name="Test Resource", description="A test resource", + type="resource", ) ] From 976ce3961f5c587e021b7625c4c15f12e4bd5f4b Mon Sep 17 00:00:00 2001 From: bzsurbhi Date: Mon, 19 May 2025 15:48:33 -0700 Subject: [PATCH 2/2] fix: add status field in Resource class and Resource as a return type in CallToolResult --- examples/servers/simple-resource/mcp_simple_resource/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/servers/simple-resource/mcp_simple_resource/server.py b/examples/servers/simple-resource/mcp_simple_resource/server.py index 06f567fbe..d06ac929a 100644 --- a/examples/servers/simple-resource/mcp_simple_resource/server.py +++ b/examples/servers/simple-resource/mcp_simple_resource/server.py @@ -30,6 +30,7 @@ async def list_resources() -> list[types.Resource]: name=name, description=f"A sample text resource named {name}", mimeType="text/plain", + type="resource", ) for name in SAMPLE_RESOURCES.keys() ]