mirror of
https://github.com/aljazceru/mcp-python-sdk.git
synced 2025-12-19 14:54:24 +01:00
refactor: standardize resource response format
Introduce ReadResourceContents type to properly handle MIME types in resource responses. Breaking change in FastMCP read_resource() return type. Github-Issue:#152
This commit is contained in:
@@ -20,6 +20,7 @@ from mcp.server.fastmcp.tools import ToolManager
|
||||
from mcp.server.fastmcp.utilities.logging import configure_logging, get_logger
|
||||
from mcp.server.fastmcp.utilities.types import Image
|
||||
from mcp.server.lowlevel import Server as MCPServer
|
||||
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.shared.context import RequestContext
|
||||
@@ -197,7 +198,7 @@ class FastMCP:
|
||||
for template in templates
|
||||
]
|
||||
|
||||
async def read_resource(self, uri: AnyUrl | str) -> tuple[str | bytes, str]:
|
||||
async def read_resource(self, uri: AnyUrl | str) -> ReadResourceContents:
|
||||
"""Read a resource by URI."""
|
||||
|
||||
resource = await self._resource_manager.get_resource(uri)
|
||||
@@ -206,7 +207,7 @@ class FastMCP:
|
||||
|
||||
try:
|
||||
content = await resource.read()
|
||||
return (content, resource.mime_type)
|
||||
return ReadResourceContents(content=content, mime_type=resource.mime_type)
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading resource {uri}: {e}")
|
||||
raise ResourceError(str(e))
|
||||
@@ -608,7 +609,7 @@ class Context(BaseModel):
|
||||
progress_token=progress_token, progress=progress, total=total
|
||||
)
|
||||
|
||||
async def read_resource(self, uri: str | AnyUrl) -> tuple[str | bytes, str]:
|
||||
async def read_resource(self, uri: str | AnyUrl) -> ReadResourceContents:
|
||||
"""Read a resource by URI.
|
||||
|
||||
Args:
|
||||
|
||||
9
src/mcp/server/lowlevel/helper_types.py
Normal file
9
src/mcp/server/lowlevel/helper_types.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReadResourceContents:
|
||||
"""Contents returned from a read_resource call."""
|
||||
|
||||
content: str | bytes
|
||||
mime_type: str | None = None
|
||||
@@ -74,6 +74,7 @@ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStre
|
||||
from pydantic import AnyUrl
|
||||
|
||||
import mcp.types as types
|
||||
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.server.stdio import stdio_server as stdio_server
|
||||
@@ -253,20 +254,20 @@ class Server:
|
||||
|
||||
def read_resource(self):
|
||||
def decorator(
|
||||
func: Callable[[AnyUrl], Awaitable[str | bytes | tuple[str | bytes, str]]],
|
||||
func: Callable[[AnyUrl], Awaitable[str | bytes | ReadResourceContents]],
|
||||
):
|
||||
logger.debug("Registering handler for ReadResourceRequest")
|
||||
|
||||
async def handler(req: types.ReadResourceRequest):
|
||||
result = await func(req.params.uri)
|
||||
|
||||
def create_content(data: str | bytes, mime_type: str):
|
||||
def create_content(data: str | bytes, mime_type: str | None):
|
||||
match data:
|
||||
case str() as data:
|
||||
return types.TextResourceContents(
|
||||
uri=req.params.uri,
|
||||
text=data,
|
||||
mimeType=mime_type,
|
||||
mimeType=mime_type or "text/plain",
|
||||
)
|
||||
case bytes() as data:
|
||||
import base64
|
||||
@@ -274,34 +275,31 @@ class Server:
|
||||
return types.BlobResourceContents(
|
||||
uri=req.params.uri,
|
||||
blob=base64.urlsafe_b64encode(data).decode(),
|
||||
mimeType=mime_type,
|
||||
mimeType=mime_type or "application/octet-stream",
|
||||
)
|
||||
|
||||
match result:
|
||||
case str() | bytes() as data:
|
||||
default_mime = (
|
||||
"text/plain"
|
||||
if isinstance(data, str)
|
||||
else "application/octet-stream"
|
||||
)
|
||||
content = create_content(data, default_mime)
|
||||
return types.ServerResult(
|
||||
types.ReadResourceResult(
|
||||
contents=[content],
|
||||
)
|
||||
)
|
||||
case (data, mime_type):
|
||||
content = create_content(data, mime_type)
|
||||
return types.ServerResult(
|
||||
types.ReadResourceResult(
|
||||
contents=[content],
|
||||
)
|
||||
warnings.warn(
|
||||
"Returning str or bytes from read_resource is deprecated. "
|
||||
"Use ReadResourceContents instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
content = create_content(data, None)
|
||||
case ReadResourceContents() as contents:
|
||||
content = create_content(contents.content, contents.mime_type)
|
||||
case _:
|
||||
raise ValueError(
|
||||
f"Unexpected return type from read_resource: {type(result)}"
|
||||
)
|
||||
|
||||
return types.ServerResult(
|
||||
types.ReadResourceResult(
|
||||
contents=[content],
|
||||
)
|
||||
)
|
||||
|
||||
self.request_handlers[types.ReadResourceRequest] = handler
|
||||
return func
|
||||
|
||||
|
||||
Reference in New Issue
Block a user