fix: change Resource URI fields from AnyUrl to str (#1863)

This commit is contained in:
Felix Weinberger 2026-01-16 08:58:57 +00:00 • committed by GitHub
commit cfb2909631
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 343 additions and 179 deletions

View file

@ -2044,8 +2044,6 @@ For servers that need to handle large datasets, the low-level server provides pa
Example of implementing pagination with MCP server decorators.
"""
from pydantic import AnyUrl
import mcp.types as types
from mcp.server.lowlevel import Server
@ -2070,7 +2068,7 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types
# Get page of resources
page_items = [
types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}")
types.Resource(uri=f"resource://items/{item}", name=item, description=f"Description for {item}")
for item in ITEMS[start:end]
]

View file

@ -116,6 +116,49 @@ result = await session.list_resources(params=PaginatedRequestParams(cursor="next
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
```
### Resource URI type changed from `AnyUrl` to `str`
The `uri` field on resource-related types now uses `str` instead of Pydantic's `AnyUrl`. This aligns with the [MCP specification schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) which defines URIs as plain strings (`uri: string`) without strict URL validation. This change allows relative paths like `users/me` that were previously rejected.
**Before (v1):**
```python
from pydantic import AnyUrl
from mcp.types import Resource
# Required wrapping in AnyUrl
resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validation
```
**After (v2):**
```python
from mcp.types import Resource
# Plain strings accepted
resource = Resource(name="test", uri="users/me") # Works
resource = Resource(name="test", uri="custom://scheme") # Works
resource = Resource(name="test", uri="https://example.com") # Works
```
If your code passes `AnyUrl` objects to URI fields, convert them to strings:
```python
# If you have an AnyUrl from elsewhere
uri = str(my_any_url) # Convert to string
```
Affected types:
- `Resource.uri`
- `ReadResourceRequestParams.uri`
- `ResourceContents.uri` (and subclasses `TextResourceContents`, `BlobResourceContents`)
- `SubscribeRequestParams.uri`
- `UnsubscribeRequestParams.uri`
- `ResourceUpdatedNotificationParams.uri`
The `ClientSession.read_resource()`, `subscribe_resource()`, and `unsubscribe_resource()` methods now accept both `str` and `AnyUrl` for backwards compatibility.
## Deprecations
<!-- Add deprecations below -->

View file

@ -29,7 +29,7 @@ from mcp.types import (
TextContent,
TextResourceContents,
)
from pydantic import AnyUrl, BaseModel, Field
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
@ -114,7 +114,7 @@ def test_embedded_resource() -> list[EmbeddedResource]:
EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl("test://embedded-resource"),
uri="test://embedded-resource",
mimeType="text/plain",
text="This is an embedded resource content.",
),
@ -131,7 +131,7 @@ def test_multiple_content_types() -> list[TextContent | ImageContent | EmbeddedR
EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl("test://mixed-content-resource"),
uri="test://mixed-content-resource",
mimeType="application/json",
text='{"test": "data", "value": 123}',
),
@ -372,7 +372,7 @@ def test_prompt_with_embedded_resource(resourceUri: str) -> list[UserMessage]:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl(resourceUri),
uri=resourceUri,
mimeType="text/plain",
text="Embedded resource content for testing.",
),
@ -402,13 +402,13 @@ async def handle_set_logging_level(level: str) -> None:
# For conformance testing, we just acknowledge the request
async def handle_subscribe(uri: AnyUrl) -> None:
async def handle_subscribe(uri: str) -> None:
"""Handle resource subscription"""
resource_subscriptions.add(str(uri))
logger.info(f"Subscribed to resource: {uri}")
async def handle_unsubscribe(uri: AnyUrl) -> None:
async def handle_unsubscribe(uri: str) -> None:
"""Handle resource unsubscription"""
resource_subscriptions.discard(str(uri))
logger.info(f"Unsubscribed from resource: {uri}")

View file

@ -11,7 +11,6 @@ import anyio
import click
import mcp.types as types
from mcp.server.lowlevel import Server
from pydantic import AnyUrl
from starlette.requests import Request
# Sample data - in real scenarios, this might come from a database
@ -27,7 +26,7 @@ SAMPLE_TOOLS = [
SAMPLE_RESOURCES = [
types.Resource(
uri=AnyUrl(f"file:///path/to/resource_{i}.txt"),
uri=f"file:///path/to/resource_{i}.txt",
name=f"resource_{i}",
description=f"This is sample resource number {i}",
)
@ -160,7 +159,7 @@ def main(port: int, transport: str) -> int:
# Implement read_resource handler
@app.read_resource()
async def read_resource(uri: AnyUrl) -> str:
async def read_resource(uri: str) -> str:
# Find the resource in our sample data
resource = next((r for r in SAMPLE_RESOURCES if r.uri == uri), None)
if not resource:

View file

@ -3,7 +3,6 @@ import click
import mcp.types as types
from mcp.server.lowlevel import Server
from mcp.server.lowlevel.helper_types import ReadResourceContents
from pydantic import AnyUrl, FileUrl
from starlette.requests import Request
SAMPLE_RESOURCES = {
@ -37,7 +36,7 @@ def main(port: int, transport: str) -> int:
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri=FileUrl(f"file:///{name}.txt"),
uri=f"file:///{name}.txt",
name=name,
title=SAMPLE_RESOURCES[name]["title"],
description=f"A sample text resource named {name}",
@ -47,10 +46,13 @@ def main(port: int, transport: str) -> int:
]
@app.read_resource()
async def read_resource(uri: AnyUrl):
if uri.path is None:
async def read_resource(uri: str):
from urllib.parse import urlparse
parsed = urlparse(uri)
if not parsed.path:
raise ValueError(f"Invalid resource path: {uri}")
name = uri.path.replace(".txt", "").lstrip("/")
name = parsed.path.replace(".txt", "").lstrip("/")
if name not in SAMPLE_RESOURCES:
raise ValueError(f"Unknown resource: {uri}")

View file

@ -8,7 +8,6 @@ import click
import mcp.types as types
from mcp.server.lowlevel import Server
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from pydantic import AnyUrl
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Mount
@ -74,7 +73,7 @@ def main(
# This will send a resource notificaiton though standalone SSE
# established by GET request
await ctx.session.send_resource_updated(uri=AnyUrl("http:///test_resource"))
await ctx.session.send_resource_updated(uri="http:///test_resource")
return [
types.TextContent(
type="text",

View file

@ -2,8 +2,6 @@
Example of implementing pagination with MCP server decorators.
"""
from pydantic import AnyUrl
import mcp.types as types
from mcp.server.lowlevel import Server
@ -28,7 +26,7 @@ async def list_resources_paginated(request: types.ListResourcesRequest) -> types
# Get page of resources
page_items = [
types.Resource(uri=AnyUrl(f"resource://items/{item}"), name=item, description=f"Description for {item}")
types.Resource(uri=f"resource://items/{item}", name=item, description=f"Description for {item}")
for item in ITEMS[start:end]
]

View file

@ -279,24 +279,24 @@ class ClientSession(
types.ListResourceTemplatesResult,
)
async def read_resource(self, uri: AnyUrl) -> types.ReadResourceResult:
async def read_resource(self, uri: str | AnyUrl) -> types.ReadResourceResult:
"""Send a resources/read request."""
return await self.send_request(
types.ClientRequest(types.ReadResourceRequest(params=types.ReadResourceRequestParams(uri=uri))),
types.ClientRequest(types.ReadResourceRequest(params=types.ReadResourceRequestParams(uri=str(uri)))),
types.ReadResourceResult,
)
async def subscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
async def subscribe_resource(self, uri: str | AnyUrl) -> types.EmptyResult:
"""Send a resources/subscribe request."""
return await self.send_request( # pragma: no cover
types.ClientRequest(types.SubscribeRequest(params=types.SubscribeRequestParams(uri=uri))),
types.ClientRequest(types.SubscribeRequest(params=types.SubscribeRequestParams(uri=str(uri)))),
types.EmptyResult,
)
async def unsubscribe_resource(self, uri: AnyUrl) -> types.EmptyResult:
async def unsubscribe_resource(self, uri: str | AnyUrl) -> types.EmptyResult:
"""Send a resources/unsubscribe request."""
return await self.send_request( # pragma: no cover
types.ClientRequest(types.UnsubscribeRequest(params=types.UnsubscribeRequestParams(uri=uri))),
types.ClientRequest(types.UnsubscribeRequest(params=types.UnsubscribeRequestParams(uri=str(uri)))),
types.EmptyResult,
)

View file

@ -1,14 +1,12 @@
"""Base classes and interfaces for FastMCP resources."""
import abc
from typing import Annotated, Any
from typing import Any
from pydantic import (
AnyUrl,
BaseModel,
ConfigDict,
Field,
UrlConstraints,
ValidationInfo,
field_validator,
)
@ -21,7 +19,7 @@ class Resource(BaseModel, abc.ABC):
model_config = ConfigDict(validate_default=True)
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource")
uri: str = Field(default=..., description="URI of the resource")
name: str | None = Field(description="Name of the resource", default=None)
title: str | None = Field(description="Human-readable title of the resource", default=None)
description: str | None = Field(description="Description of the resource", default=None)

View file

@ -11,7 +11,7 @@ import anyio.to_thread
import httpx
import pydantic
import pydantic_core
from pydantic import AnyUrl, Field, ValidationInfo, validate_call
from pydantic import Field, ValidationInfo, validate_call
from mcp.server.fastmcp.resources.base import Resource
from mcp.types import Annotations, Icon
@ -94,7 +94,7 @@ class FunctionResource(Resource):
fn = validate_call(fn)
return cls(
uri=AnyUrl(uri),
uri=uri,
name=func_name,
title=title,
description=description or fn.__doc__ or "",

View file

@ -79,7 +79,6 @@ from typing import Any, Generic, TypeAlias, cast
import anyio
import jsonschema
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from pydantic import AnyUrl
from typing_extensions import TypeVar
import mcp.types as types
@ -337,7 +336,7 @@ class Server(Generic[LifespanResultT, RequestT]):
def read_resource(self):
def decorator(
func: Callable[[AnyUrl], Awaitable[str | bytes | Iterable[ReadResourceContents]]],
func: Callable[[str], Awaitable[str | bytes | Iterable[ReadResourceContents]]],
):
logger.debug("Registering handler for ReadResourceRequest")
@ -412,7 +411,7 @@ class Server(Generic[LifespanResultT, RequestT]):
return decorator
def subscribe_resource(self): # pragma: no cover
def decorator(func: Callable[[AnyUrl], Awaitable[None]]):
def decorator(func: Callable[[str], Awaitable[None]]):
logger.debug("Registering handler for SubscribeRequest")
async def handler(req: types.SubscribeRequest):
@ -425,7 +424,7 @@ class Server(Generic[LifespanResultT, RequestT]):
return decorator
def unsubscribe_resource(self): # pragma: no cover
def decorator(func: Callable[[AnyUrl], Awaitable[None]]):
def decorator(func: Callable[[str], Awaitable[None]]):
logger.debug("Registering handler for UnsubscribeRequest")
async def handler(req: types.UnsubscribeRequest):

View file

@ -227,12 +227,12 @@ class ServerSession(
related_request_id,
)
async def send_resource_updated(self, uri: AnyUrl) -> None: # pragma: no cover
async def send_resource_updated(self, uri: str | AnyUrl) -> None: # pragma: no cover
"""Send a resource updated notification."""
await self.send_notification(
types.ServerNotification(
types.ResourceUpdatedNotification(
params=types.ResourceUpdatedNotificationParams(uri=uri),
params=types.ResourceUpdatedNotificationParams(uri=str(uri)),
)
)
)

View file

@ -5,7 +5,6 @@ from datetime import datetime
from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
from pydantic.networks import AnyUrl, UrlConstraints
LATEST_PROTOCOL_VERSION = "2025-11-25"
@ -756,7 +755,7 @@ class Annotations(BaseModel):
class Resource(BaseMetadata):
"""A known resource that the server is capable of reading."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""The URI of this resource."""
description: str | None = None
"""A description of what this resource represents."""
@ -827,7 +826,7 @@ class ListResourceTemplatesResult(PaginatedResult):
class ReadResourceRequestParams(RequestParams):
"""Parameters for reading a resource."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""
The URI of the resource to read. The URI can use any protocol; it is up to the
server how to interpret it.
@ -845,7 +844,7 @@ class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/
class ResourceContents(BaseModel):
"""The contents of a specific resource or sub-resource."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""The URI of this resource."""
mimeType: str | None = None
"""The MIME type of this resource, if known."""
@ -895,7 +894,7 @@ class ResourceListChangedNotification(
class SubscribeRequestParams(RequestParams):
"""Parameters for subscribing to a resource."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""
The URI of the resource to subscribe to. The URI can use any protocol; it is up to
the server how to interpret it.
@ -916,7 +915,7 @@ class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscr
class UnsubscribeRequestParams(RequestParams):
"""Parameters for unsubscribing from a resource."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""The URI of the resource to unsubscribe from."""
model_config = ConfigDict(extra="allow")
@ -934,7 +933,7 @@ class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/un
class ResourceUpdatedNotificationParams(NotificationParams):
"""Parameters for resource update notifications."""
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
uri: str
"""
The URI of the resource that has been updated. This might be a sub-resource of the
one that the client actually subscribed to.

View file

@ -70,9 +70,9 @@ 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"),
types.Resource(uri="test://image", name="test image", mimeType="image/png"),
types.Resource(
uri=AnyUrl("test://image_bytes"),
uri="test://image_bytes",
name="test image bytes",
mimeType="image/png",
),
@ -83,7 +83,7 @@ async def test_lowlevel_resource_mime_type():
return test_resources
@server.read_resource()
async def handle_read_resource(uri: AnyUrl):
async def handle_read_resource(uri: str):
if str(uri) == "test://image":
return [ReadResourceContents(content=base64_string, mime_type="image/png")]
elif str(uri) == "test://image_bytes":

View file

@ -0,0 +1,132 @@
"""Tests for issue #1574: Python SDK incorrectly validates Resource URIs.
The Python SDK previously used Pydantic's AnyUrl for URI fields, which rejected
relative paths like 'users/me' that are valid according to the MCP spec and
accepted by the TypeScript SDK.
The fix changed URI fields to plain strings to match the spec, which defines
uri fields as strings with no JSON Schema format validation.
These tests verify the fix works end-to-end through the JSON-RPC protocol.
"""
import pytest
from mcp import types
from mcp.server.lowlevel import Server
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.shared.memory import (
create_connected_server_and_client_session as client_session,
)
pytestmark = pytest.mark.anyio
async def test_relative_uri_roundtrip():
"""Relative URIs survive the full server-client JSON-RPC roundtrip.
This is the critical regression test - if someone reintroduces AnyUrl,
the server would fail to serialize resources with relative URIs,
or the URI would be transformed during the roundtrip.
"""
server = Server("test")
@server.list_resources()
async def list_resources():
return [
types.Resource(name="user", uri="users/me"),
types.Resource(name="config", uri="./config"),
types.Resource(name="parent", uri="../parent/resource"),
]
@server.read_resource()
async def read_resource(uri: str):
return [
ReadResourceContents(
content=f"data for {uri}",
mime_type="text/plain",
)
]
async with client_session(server) as client:
# List should return the exact URIs we specified
resources = await client.list_resources()
uri_map = {r.uri: r for r in resources.resources}
assert "users/me" in uri_map, f"Expected 'users/me' in {list(uri_map.keys())}"
assert "./config" in uri_map, f"Expected './config' in {list(uri_map.keys())}"
assert "../parent/resource" in uri_map, f"Expected '../parent/resource' in {list(uri_map.keys())}"
# Read should work with each relative URI and preserve it in the response
for uri_str in ["users/me", "./config", "../parent/resource"]:
result = await client.read_resource(uri_str)
assert len(result.contents) == 1
assert result.contents[0].uri == uri_str
async def test_custom_scheme_uri_roundtrip():
"""Custom scheme URIs work through the protocol.
Some MCP servers use custom schemes like "custom://resource".
These should work end-to-end.
"""
server = Server("test")
@server.list_resources()
async def list_resources():
return [
types.Resource(name="custom", uri="custom://my-resource"),
types.Resource(name="file", uri="file:///path/to/file"),
]
@server.read_resource()
async def read_resource(uri: str):
return [ReadResourceContents(content="data", mime_type="text/plain")]
async with client_session(server) as client:
resources = await client.list_resources()
uri_map = {r.uri: r for r in resources.resources}
assert "custom://my-resource" in uri_map
assert "file:///path/to/file" in uri_map
# Read with custom scheme
result = await client.read_resource("custom://my-resource")
assert len(result.contents) == 1
def test_uri_json_roundtrip_preserves_value():
"""URI is preserved exactly through JSON serialization.
This catches any Pydantic validation or normalization that would
alter the URI during the JSON-RPC message flow.
"""
test_uris = [
"users/me",
"custom://resource",
"./relative",
"../parent",
"file:///absolute/path",
"https://example.com/path",
]
for uri_str in test_uris:
resource = types.Resource(name="test", uri=uri_str)
json_data = resource.model_dump(mode="json")
restored = types.Resource.model_validate(json_data)
assert restored.uri == uri_str, f"URI mutated: {uri_str} -> {restored.uri}"
def test_resource_contents_uri_json_roundtrip():
"""TextResourceContents URI is preserved through JSON serialization."""
test_uris = ["users/me", "./relative", "custom://resource"]
for uri_str in test_uris:
contents = types.TextResourceContents(
uri=uri_str,
text="data",
mimeType="text/plain",
)
json_data = contents.model_dump(mode="json")
restored = types.TextResourceContents.model_validate(json_data)
assert restored.uri == uri_str, f"URI mutated: {uri_str} -> {restored.uri}"

View file

@ -13,7 +13,6 @@ import base64
from typing import cast
import pytest
from pydantic import AnyUrl
from mcp.server.lowlevel.helper_types import ReadResourceContents
from mcp.server.lowlevel.server import Server
@ -46,7 +45,7 @@ async def test_server_base64_encoding_issue():
# Register a resource handler that returns our test data
@server.read_resource()
async def read_resource(uri: AnyUrl) -> list[ReadResourceContents]:
async def read_resource(uri: str) -> list[ReadResourceContents]:
return [ReadResourceContents(content=binary_data, mime_type="application/octet-stream")]
# Get the handler directly from the server
@ -54,7 +53,7 @@ async def test_server_base64_encoding_issue():
# Create a request
request = ReadResourceRequest(
params=ReadResourceRequestParams(uri=AnyUrl("test://resource")),
params=ReadResourceRequestParams(uri="test://resource"),
)
# Call the handler to get the response

View file

@ -1,7 +1,6 @@
from typing import Any
import pytest
from pydantic import FileUrl
from mcp.server.fastmcp.prompts.base import AssistantMessage, Message, Prompt, TextContent, UserMessage
from mcp.types import EmbeddedResource, TextResourceContents
@ -95,7 +94,7 @@ class TestRenderPrompt:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
),
@ -108,7 +107,7 @@ class TestRenderPrompt:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
),
@ -127,7 +126,7 @@ class TestRenderPrompt:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
),
@ -143,7 +142,7 @@ class TestRenderPrompt:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
),
@ -162,7 +161,7 @@ class TestRenderPrompt:
"content": {
"type": "resource",
"resource": {
"uri": FileUrl("file://file.txt"),
"uri": "file://file.txt",
"text": "File contents",
"mimeType": "text/plain",
},
@ -175,7 +174,7 @@ class TestRenderPrompt:
content=EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=FileUrl("file://file.txt"),
uri="file://file.txt",
text="File contents",
mimeType="text/plain",
),

View file

@ -3,7 +3,6 @@ from pathlib import Path
from tempfile import NamedTemporaryFile
import pytest
from pydantic import FileUrl
from mcp.server.fastmcp.resources import FileResource
@ -31,7 +30,7 @@ class TestFileResource:
def test_file_resource_creation(self, temp_file: Path):
"""Test creating a FileResource."""
resource = FileResource(
uri=FileUrl(temp_file.as_uri()),
uri=temp_file.as_uri(),
name="test",
description="test file",
path=temp_file,
@ -46,7 +45,7 @@ class TestFileResource:
def test_file_resource_str_path_conversion(self, temp_file: Path):
"""Test FileResource handles string paths."""
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=Path(str(temp_file)),
)
@ -57,7 +56,7 @@ class TestFileResource:
async def test_read_text_file(self, temp_file: Path):
"""Test reading a text file."""
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -69,7 +68,7 @@ class TestFileResource:
async def test_read_binary_file(self, temp_file: Path):
"""Test reading a file as binary."""
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
is_binary=True,
@ -82,7 +81,7 @@ class TestFileResource:
"""Test error on relative path."""
with pytest.raises(ValueError, match="Path must be absolute"):
FileResource(
uri=FileUrl("file:///test.txt"),
uri="file:///test.txt",
name="test",
path=Path("test.txt"),
)
@ -93,7 +92,7 @@ class TestFileResource:
# Create path to non-existent file
missing = temp_file.parent / "missing.txt"
resource = FileResource(
uri=FileUrl("file:///missing.txt"),
uri="file:///missing.txt",
name="test",
path=missing,
)
@ -107,7 +106,7 @@ class TestFileResource:
temp_file.chmod(0o000) # Remove all permissions
try:
resource = FileResource(
uri=FileUrl(temp_file.as_uri()),
uri=temp_file.as_uri(),
name="test",
path=temp_file,
)

View file

@ -1,5 +1,5 @@
import pytest
from pydantic import AnyUrl, BaseModel
from pydantic import BaseModel
from mcp.server.fastmcp.resources import FunctionResource
@ -14,7 +14,7 @@ class TestFunctionResource:
return "test content"
resource = FunctionResource(
uri=AnyUrl("fn://test"),
uri="fn://test",
name="test",
description="test function",
fn=my_func,
@ -33,7 +33,7 @@ class TestFunctionResource:
return "Hello, world!"
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=get_data,
)
@ -49,7 +49,7 @@ class TestFunctionResource:
return b"Hello, world!"
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=get_data,
)
@ -64,7 +64,7 @@ class TestFunctionResource:
return {"key": "value"}
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=get_data,
)
@ -80,7 +80,7 @@ class TestFunctionResource:
raise ValueError("Test error")
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=failing_func,
)
@ -95,7 +95,7 @@ class TestFunctionResource:
name: str
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=lambda: MyModel(name="test"),
)
@ -114,7 +114,7 @@ class TestFunctionResource:
return CustomData()
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=get_data,
)
@ -129,7 +129,7 @@ class TestFunctionResource:
return "Hello, world!"
resource = FunctionResource(
uri=AnyUrl("function://test"),
uri="function://test",
name="test",
fn=get_data,
)
@ -154,7 +154,7 @@ class TestFunctionResource:
assert resource.description == "get_data returns a string"
assert resource.mime_type == "text/plain"
assert resource.name == "test"
assert resource.uri == AnyUrl("function://test")
assert resource.uri == "function://test"
class TestFunctionResourceMetadata:

View file

@ -2,7 +2,7 @@ from pathlib import Path
from tempfile import NamedTemporaryFile
import pytest
from pydantic import AnyUrl, FileUrl
from pydantic import AnyUrl
from mcp.server.fastmcp.resources import FileResource, FunctionResource, ResourceManager, ResourceTemplate
@ -31,7 +31,7 @@ class TestResourceManager:
"""Test adding a resource."""
manager = ResourceManager()
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -43,7 +43,7 @@ class TestResourceManager:
"""Test adding the same resource twice."""
manager = ResourceManager()
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -56,7 +56,7 @@ class TestResourceManager:
"""Test warning on duplicate resources."""
manager = ResourceManager()
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -68,7 +68,7 @@ class TestResourceManager:
"""Test disabling warning on duplicate resources."""
manager = ResourceManager(warn_on_duplicate_resources=False)
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -81,7 +81,7 @@ class TestResourceManager:
"""Test getting a resource by URI."""
manager = ResourceManager()
resource = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test",
path=temp_file,
)
@ -120,12 +120,12 @@ class TestResourceManager:
"""Test listing all resources."""
manager = ResourceManager()
resource1 = FileResource(
uri=FileUrl(f"file://{temp_file}"),
uri=f"file://{temp_file}",
name="test1",
path=temp_file,
)
resource2 = FileResource(
uri=FileUrl(f"file://{temp_file}2"),
uri=f"file://{temp_file}2",
name="test2",
path=temp_file,
)

Some files were not shown because too many files have changed in this diff Show more