mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 13:04:18 +02:00
Indicate that Image class is for returns
This commit is contained in:
parent
a4e233711f
commit
b81d1a11ec
3 changed files with 39 additions and 42 deletions
36
README.md
36
README.md
|
|
@ -332,32 +332,30 @@ The `Context` object provides:
|
|||
|
||||
### Images
|
||||
|
||||
Easily handle image input and output using the `fastmcp.Image` helper class.
|
||||
Easily handle image outputs using the `fastmcp.Image` helper class.
|
||||
|
||||
<Tip>
|
||||
The below code requires the `pillow` library to be installed.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Image
|
||||
from PIL import Image as PILImage
|
||||
import io
|
||||
from mcp.server.fastmcp import FastMCP, Image
|
||||
try:
|
||||
from PIL import Image as PILImage
|
||||
except ImportError:
|
||||
raise ImportError("Please install the `pillow` library to run this example.")
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
mcp = FastMCP("Image Demo")
|
||||
|
||||
@mcp.tool()
|
||||
def create_thumbnail(image_data: Image) -> Image:
|
||||
"""Creates a 100x100 thumbnail from the provided image."""
|
||||
img = PILImage.open(io.BytesIO(image_data.data)) # Assumes image_data received as Image with bytes
|
||||
def create_thumbnail(image_path: str) -> Image:
|
||||
"""Create a thumbnail from an image"""
|
||||
img = PILImage.open(image_path)
|
||||
img.thumbnail((100, 100))
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format="PNG")
|
||||
# Return a new Image object with the thumbnail data
|
||||
return Image(data=buffer.getvalue(), format="png")
|
||||
|
||||
@mcp.tool()
|
||||
def load_image_from_disk(path: str) -> Image:
|
||||
"""Loads an image from the specified path."""
|
||||
# Handles reading file and detecting format based on extension
|
||||
return Image(path=path)
|
||||
return Image(data=img.tobytes(), format="png")
|
||||
```
|
||||
FastMCP handles the conversion to/from the base64-encoded format required by the MCP protocol.
|
||||
Return the `Image` helper class from your tool to send an image to the client. The `Image` helper class handles the conversion to/from the base64-encoded format required by the MCP protocol. It works with either a path to an image file, or a bytes object.
|
||||
|
||||
|
||||
### MCP Clients
|
||||
|
|
|
|||
|
|
@ -209,13 +209,18 @@ FastMCP automatically converts the value returned by your function into the appr
|
|||
- **`str`**: Sent as `TextContent`.
|
||||
- **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
|
||||
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
|
||||
- **`fastmcp.utilities.types.Image`**: A helper class to easily return image data. Sent as `ImageContent`.
|
||||
- **`fastmcp.Image`**: A helper class for easily returning image data. Sent as `ImageContent`.
|
||||
- **`None`**: Results in an empty response (no content is sent back to the client).
|
||||
|
||||
```python
|
||||
from fastmcp.utilities.types import Image
|
||||
from PIL import Image as PILImage
|
||||
from fastmcp import FastMCP, Image
|
||||
import io
|
||||
try:
|
||||
from PIL import Image as PILImage
|
||||
except ImportError:
|
||||
raise ImportError("Please install the `pillow` library to run this example.")
|
||||
|
||||
mcp = FastMCP("Image Demo")
|
||||
|
||||
@mcp.tool()
|
||||
def generate_image(width: int, height: int, color: str) -> Image:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import json
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
from mcp.types import ImageContent, TextContent
|
||||
from pydantic import BaseModel
|
||||
|
||||
from fastmcp import Context, FastMCP, Image
|
||||
from fastmcp.exceptions import NotFoundError, ToolError
|
||||
from fastmcp.tools import ToolManager
|
||||
from fastmcp.tools.tool import Tool
|
||||
|
|
@ -68,6 +70,18 @@ class TestAddTools:
|
|||
assert "age" in tool.parameters["$defs"]["UserInput"]["properties"]
|
||||
assert "flag" in tool.parameters["properties"]
|
||||
|
||||
async def test_tool_with_image_return(self):
|
||||
def image_tool(data: bytes) -> Image:
|
||||
return Image(data=data)
|
||||
|
||||
manager = ToolManager()
|
||||
manager.add_tool_from_fn(image_tool)
|
||||
|
||||
tool = manager.get_tool("image_tool")
|
||||
result = await tool.run({"data": "test.png"})
|
||||
assert tool.parameters["properties"]["data"]["type"] == "string"
|
||||
assert isinstance(result[0], ImageContent)
|
||||
|
||||
def test_add_invalid_tool(self):
|
||||
manager = ToolManager()
|
||||
with pytest.raises(AttributeError):
|
||||
|
|
@ -263,7 +277,6 @@ class TestCallTools:
|
|||
result = await manager.call_tool("double", {"n": 5})
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
from mcp.types import TextContent
|
||||
|
||||
assert isinstance(result[0], TextContent)
|
||||
assert result[0].text == "10"
|
||||
|
|
@ -279,7 +292,6 @@ class TestCallTools:
|
|||
result = await manager.call_tool("add", {"a": 1})
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
from mcp.types import TextContent
|
||||
|
||||
assert isinstance(result[0], TextContent)
|
||||
assert result[0].text == "2"
|
||||
|
|
@ -307,7 +319,6 @@ class TestCallTools:
|
|||
manager = ToolManager()
|
||||
manager.add_tool_from_fn(sum_vals)
|
||||
# Try both with plain list and with JSON list
|
||||
from mcp.types import TextContent
|
||||
|
||||
result = await manager.call_tool("sum_vals", {"vals": "[1, 2, 3]"})
|
||||
assert isinstance(result, list)
|
||||
|
|
@ -329,7 +340,6 @@ class TestCallTools:
|
|||
|
||||
manager = ToolManager()
|
||||
manager.add_tool_from_fn(concat_strs)
|
||||
from mcp.types import TextContent
|
||||
|
||||
# Try both with plain python object and with JSON list
|
||||
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]})
|
||||
|
|
@ -357,10 +367,6 @@ class TestCallTools:
|
|||
assert result[0].text == '"a"'
|
||||
|
||||
async def test_call_tool_with_complex_model(self):
|
||||
from mcp.types import TextContent
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
class MyShrimpTank(BaseModel):
|
||||
class Shrimp(BaseModel):
|
||||
name: str
|
||||
|
|
@ -397,8 +403,6 @@ class TestCallTools:
|
|||
|
||||
class TestToolSchema:
|
||||
async def test_context_arg_excluded_from_schema(self):
|
||||
from fastmcp import Context
|
||||
|
||||
def something(a: int, ctx: Context) -> int:
|
||||
return a
|
||||
|
||||
|
|
@ -415,7 +419,6 @@ class TestContextHandling:
|
|||
def test_context_parameter_detection(self):
|
||||
"""Test that context parameters are properly detected in
|
||||
Tool.from_function()."""
|
||||
from fastmcp import Context
|
||||
|
||||
def tool_with_context(x: int, ctx: Context) -> str:
|
||||
return str(x)
|
||||
|
|
@ -432,9 +435,6 @@ class TestContextHandling:
|
|||
|
||||
async def test_context_injection(self):
|
||||
"""Test that context is properly injected during tool execution."""
|
||||
from mcp.types import TextContent
|
||||
|
||||
from fastmcp import Context, FastMCP
|
||||
|
||||
def tool_with_context(x: int, ctx: Context) -> str:
|
||||
assert isinstance(ctx, Context)
|
||||
|
|
@ -453,9 +453,6 @@ class TestContextHandling:
|
|||
|
||||
async def test_context_injection_async(self):
|
||||
"""Test that context is properly injected in async tools."""
|
||||
from mcp.types import TextContent
|
||||
|
||||
from fastmcp import Context, FastMCP
|
||||
|
||||
async def async_tool(x: int, ctx: Context) -> str:
|
||||
assert isinstance(ctx, Context)
|
||||
|
|
@ -476,8 +473,6 @@ class TestContextHandling:
|
|||
"""Test that context is optional when calling tools."""
|
||||
from mcp.types import TextContent
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
def tool_with_context(x: int, ctx: Context | None = None) -> str:
|
||||
return str(x)
|
||||
|
||||
|
|
@ -492,7 +487,6 @@ class TestContextHandling:
|
|||
|
||||
async def test_context_error_handling(self):
|
||||
"""Test error handling when context injection fails."""
|
||||
from fastmcp import Context, FastMCP
|
||||
|
||||
def tool_with_context(x: int, ctx: Context) -> str:
|
||||
raise ValueError("Test error")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue