mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Run pre-commit and fix typing issues
This commit is contained in:
parent
84316cea5e
commit
57e0b03711
6 changed files with 48 additions and 24 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import aiohttp
|
||||
|
||||
from fastmcp.server import FastMCP
|
||||
from fastmcp.utilities.types import File
|
||||
|
||||
|
||||
def create_server():
|
||||
mcp = FastMCP(name="File Demo", instructions="Get files from the server or URL.")
|
||||
|
||||
|
|
@ -11,9 +13,11 @@ def create_server():
|
|||
Get a test file from the server. If the path is not provided, it defaults to 'requirements.txt'.
|
||||
"""
|
||||
return File(path=path)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_test_pdf_from_url(url: str = "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf") -> File:
|
||||
async def get_test_pdf_from_url(
|
||||
url: str = "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf",
|
||||
) -> File:
|
||||
"""
|
||||
Get a test PDF file from a URL. If the URL is not provided, it defaults to a sample PDF.
|
||||
"""
|
||||
|
|
@ -21,7 +25,9 @@ def create_server():
|
|||
async with session.get(url) as response:
|
||||
pdf_data = await response.read()
|
||||
return File(data=pdf_data, format="pdf")
|
||||
|
||||
|
||||
return mcp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_server().run(transport="sse", host="0.0.0.0", port=8001, path="/sse")
|
||||
create_server().run(transport="sse", host="0.0.0.0", port=8001, path="/sse")
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ from fastmcp.utilities.json_schema import compress_schema
|
|||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.types import (
|
||||
Audio,
|
||||
Image,
|
||||
File,
|
||||
Image,
|
||||
MCPContent,
|
||||
find_kwarg_by_type,
|
||||
get_cached_typeadapter,
|
||||
|
|
|
|||
|
|
@ -2,22 +2,22 @@
|
|||
|
||||
import base64
|
||||
import inspect
|
||||
import mimetypes
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from types import UnionType
|
||||
from typing import Annotated, TypeAlias, TypeVar, Union, get_args, get_origin
|
||||
import mimetypes
|
||||
|
||||
from mcp.types import (
|
||||
Annotations,
|
||||
AudioContent,
|
||||
BlobResourceContents,
|
||||
EmbeddedResource,
|
||||
ImageContent,
|
||||
TextContent,
|
||||
BlobResourceContents
|
||||
)
|
||||
from pydantic import BaseModel, ConfigDict, TypeAdapter
|
||||
from pydantic import AnyUrl, BaseModel, ConfigDict, TypeAdapter, UrlConstraints
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ class File:
|
|||
mime_type, _ = mimetypes.guess_type(self.path)
|
||||
if mime_type:
|
||||
return mime_type
|
||||
|
||||
|
||||
return "application/octet-stream"
|
||||
|
||||
def to_resource_content(
|
||||
|
|
@ -250,23 +250,28 @@ class File:
|
|||
if self.path:
|
||||
with open(self.path, "rb") as f:
|
||||
data = base64.b64encode(f.read()).decode()
|
||||
uri=self.path.resolve().as_uri()
|
||||
uri_str = self.path.resolve().as_uri()
|
||||
|
||||
elif self.data is not None:
|
||||
data = base64.b64encode(self.data).decode()
|
||||
uri=self.path or (self._name and f"file:///{self._name}.{self._mime_type.split('/')[1]}") or f"file:///resource.{self._mime_type.split('/')[1]}"
|
||||
if self._name:
|
||||
uri_str = f"file:///{self._name}.{self._mime_type.split('/')[1]}"
|
||||
else:
|
||||
uri_str = f"file:///resource.{self._mime_type.split('/')[1]}"
|
||||
|
||||
else:
|
||||
raise ValueError("No resource data available")
|
||||
|
||||
UriType = Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
||||
uri = TypeAdapter(UriType).validate_python(uri_str)
|
||||
resource = BlobResourceContents(
|
||||
blob=data,
|
||||
mimeType=mime_type or self._mime_type,
|
||||
uri=uri,
|
||||
)
|
||||
blob=data,
|
||||
mimeType=mime_type or self._mime_type,
|
||||
uri=uri,
|
||||
)
|
||||
|
||||
return EmbeddedResource(
|
||||
type="resource",
|
||||
resource=resource,
|
||||
annotations=annotations or self.annotations,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import pytest
|
|||
from mcp import McpError
|
||||
from mcp.types import (
|
||||
AudioContent,
|
||||
BlobResourceContents,
|
||||
EmbeddedResource,
|
||||
ImageContent,
|
||||
TextContent,
|
||||
TextResourceContents,
|
||||
BlobResourceContents,
|
||||
)
|
||||
from pydantic import AnyUrl, Field
|
||||
|
||||
|
|
@ -62,7 +62,14 @@ def tool_server():
|
|||
return [
|
||||
TextContent(type="text", text="Hello"),
|
||||
ImageContent(type="image", data="abc", mimeType="application/octet-stream"),
|
||||
EmbeddedResource(type="resource", resource=BlobResourceContents(blob="abc", mimeType="application/octet-stream", uri=AnyUrl("abc"))),
|
||||
EmbeddedResource(
|
||||
type="resource",
|
||||
resource=BlobResourceContents(
|
||||
blob=base64.b64encode(b"abc").decode(),
|
||||
mimeType="application/octet-stream",
|
||||
uri=AnyUrl("file:///test.bin"),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@mcp.tool
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class TestToolFromFunction:
|
|||
result = await tool.run({"data": "test.wav"})
|
||||
assert tool.parameters["properties"]["data"]["type"] == "string"
|
||||
assert isinstance(result[0], AudioContent)
|
||||
|
||||
|
||||
async def test_tool_with_file_return(self):
|
||||
def file_tool(data: bytes) -> File:
|
||||
return File(data=data, format="octet-stream")
|
||||
|
|
@ -620,7 +620,7 @@ class TestConvertResultToContent:
|
|||
|
||||
text_content_count = sum(isinstance(item, TextContent) for item in result)
|
||||
embedded_content_count = sum(
|
||||
isinstance(item, EmbeddedResource) and item.type == "resource"
|
||||
isinstance(item, EmbeddedResource) and item.type == "resource"
|
||||
for item in result
|
||||
)
|
||||
|
||||
|
|
@ -631,7 +631,8 @@ class TestConvertResultToContent:
|
|||
assert text_item.text == '{\n "a": 1\n}'
|
||||
|
||||
embedded_item = next(
|
||||
item for item in result
|
||||
item
|
||||
for item in result
|
||||
if isinstance(item, EmbeddedResource) and item.type == "resource"
|
||||
)
|
||||
resource = embedded_item.resource
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from types import EllipsisType
|
|||
from typing import Annotated, Any
|
||||
|
||||
import pytest
|
||||
from mcp.types import BlobResourceContents
|
||||
|
||||
from fastmcp.utilities.types import (
|
||||
Audio,
|
||||
|
|
@ -345,7 +346,9 @@ class TestFile:
|
|||
def test_get_mime_type_from_path(self, tmp_path):
|
||||
"""Test MIME type detection from file extension."""
|
||||
file_path = tmp_path / "test.txt"
|
||||
file_path.write_text("test content") # Need to write content for MIME type detection
|
||||
file_path.write_text(
|
||||
"test content"
|
||||
) # Need to write content for MIME type detection
|
||||
file = File(path=file_path)
|
||||
# The MIME type should be detected from the .txt extension
|
||||
assert file._mime_type == "text/plain"
|
||||
|
|
@ -363,7 +366,8 @@ class TestFile:
|
|||
assert resource.resource.mimeType == "text/plain"
|
||||
# Convert both to strings for comparison
|
||||
assert str(resource.resource.uri) == file_path.resolve().as_uri()
|
||||
assert resource.resource.blob == base64.b64encode(test_data).decode()
|
||||
if isinstance(resource.resource, BlobResourceContents):
|
||||
assert resource.resource.blob == base64.b64encode(test_data).decode()
|
||||
|
||||
def test_to_resource_content_with_data(self):
|
||||
"""Test conversion to ResourceContent with data."""
|
||||
|
|
@ -375,7 +379,8 @@ class TestFile:
|
|||
assert resource.resource.mimeType == "application/pdf"
|
||||
# Convert URI to string for comparison
|
||||
assert str(resource.resource.uri) == "file:///resource.pdf"
|
||||
assert resource.resource.blob == base64.b64encode(test_data).decode()
|
||||
if isinstance(resource.resource, BlobResourceContents):
|
||||
assert resource.resource.blob == base64.b64encode(test_data).decode()
|
||||
|
||||
def test_to_resource_content_error(self, monkeypatch):
|
||||
"""Test error case in to_resource_content."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue