Skip to content

fix: add status field in Resource class and Resource as a return type in CallToolResult #758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
]
Expand Down
2 changes: 2 additions & 0 deletions src/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ReadResourceResult,
Resource,
ResourcesCapability,
ResourceStatus,
ResourceUpdatedNotification,
RootsCapability,
SamplingMessage,
Expand Down Expand Up @@ -90,6 +91,7 @@
"ReadResourceRequest",
"ReadResourceResult",
"ResourcesCapability",
"ResourceStatus",
"ResourceUpdatedNotification",
"Resource",
"RootsCapability",
Expand Down
6 changes: 6 additions & 0 deletions src/mcp/server/fastmcp/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
field_validator,
)

from mcp.types import ResourceStatus as MCPResourceStatus


class Resource(BaseModel, abc.ABC):
"""Base class for all resources."""
Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions src/mcp/server/fastmcp/resources/resource_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)})
Expand Down
6 changes: 4 additions & 2 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
]
Expand Down Expand Up @@ -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 []
Expand Down
5 changes: 4 additions & 1 deletion src/mcp/server/lowlevel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,10 @@ def decorator(
...,
Awaitable[
Iterable[
types.TextContent | types.ImageContent | types.EmbeddedResource
types.TextContent
| types.ImageContent
| types.EmbeddedResource
| types.Resource
]
],
],
Expand Down
19 changes: 18 additions & 1 deletion src/mcp/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Callable
from enum import Enum
from typing import (
Annotated,
Any,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
6 changes: 5 additions & 1 deletion tests/issues/test_152_resource_mime_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
]

Expand Down
1 change: 1 addition & 0 deletions tests/shared/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ async def handle_list_resources():
uri=AnyUrl("memory://test"),
name="Test Resource",
description="A test resource",
type="resource",
)
]

Expand Down
Loading