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
4 changed files with 343 additions and 179 deletions

View file

@ -1,5 +1,4 @@
import pytest
from pydantic import AnyUrl
from typing_extensions import AsyncGenerator
from mcp.client.session import ClientSession
@ -16,7 +15,7 @@ def mcp_server() -> Server:
async def handle_list_resources(): # pragma: no cover
return [
Resource(
uri=AnyUrl("memory://test"),
uri="memory://test",
name="Test Resource",
description="A test resource",
)

View file

@ -5,6 +5,7 @@ import time
from collections.abc import AsyncGenerator, Generator
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from urllib.parse import urlparse
import anyio
import httpx
@ -12,7 +13,6 @@ import pytest
import uvicorn
from httpx_sse import ServerSentEvent
from inline_snapshot import snapshot
from pydantic import AnyUrl
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import Response
@ -61,13 +61,14 @@ class ServerTest(Server): # pragma: no cover
super().__init__(SERVER_NAME)
@self.read_resource()
async def handle_read_resource(uri: AnyUrl) -> str | bytes:
if uri.scheme == "foobar":
return f"Read {uri.host}"
elif uri.scheme == "slow":
async def handle_read_resource(uri: str) -> str | bytes:
parsed = urlparse(uri)
if parsed.scheme == "foobar":
return f"Read {parsed.netloc}"
if parsed.scheme == "slow":
# Simulate a slow resource
await anyio.sleep(2.0)
return f"Slow response from {uri.host}"
return f"Slow response from {parsed.netloc}"
raise McpError(error=ErrorData(code=404, message="OOPS! no resource with that URI was found"))
@ -254,7 +255,7 @@ async def test_sse_client_happy_request_and_response(
initialized_sse_client_session: ClientSession,
) -> None:
session = initialized_sse_client_session
response = await session.read_resource(uri=AnyUrl("foobar://should-work"))
response = await session.read_resource(uri="foobar://should-work")
assert len(response.contents) == 1
assert isinstance(response.contents[0], TextResourceContents)
assert response.contents[0].text == "Read should-work"
@ -266,7 +267,7 @@ async def test_sse_client_exception_handling(
) -> None:
session = initialized_sse_client_session
with pytest.raises(McpError, match="OOPS! no resource with that URI was found"):
await session.read_resource(uri=AnyUrl("xxx://will-not-work"))
await session.read_resource(uri="xxx://will-not-work")
@pytest.mark.anyio
@ -277,12 +278,12 @@ async def test_sse_client_timeout( # pragma: no cover
session = initialized_sse_client_session
# sanity check that normal, fast responses are working
response = await session.read_resource(uri=AnyUrl("foobar://1"))
response = await session.read_resource(uri="foobar://1")
assert isinstance(response, ReadResourceResult)
with anyio.move_on_after(3):
with pytest.raises(McpError, match="Read timed out"):
response = await session.read_resource(uri=AnyUrl("slow://2"))
response = await session.read_resource(uri="slow://2")
# we should receive an error here
return

View file

@ -13,6 +13,7 @@ import time
from collections.abc import Generator
from typing import Any
from unittest.mock import MagicMock
from urllib.parse import urlparse
import anyio
import httpx
@ -20,7 +21,6 @@ import pytest
import requests
import uvicorn
from httpx_sse import ServerSentEvent
from pydantic import AnyUrl
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.routing import Mount
@ -137,13 +137,14 @@ class ServerTest(Server): # pragma: no cover
self._lock = None # Will be initialized in async context
@self.read_resource()
async def handle_read_resource(uri: AnyUrl) -> str | bytes:
if uri.scheme == "foobar":
return f"Read {uri.host}"
elif uri.scheme == "slow":
async def handle_read_resource(uri: str) -> str | bytes:
parsed = urlparse(uri)
if parsed.scheme == "foobar":
return f"Read {parsed.netloc}"
if parsed.scheme == "slow":
# Simulate a slow resource
await anyio.sleep(2.0)
return f"Slow response from {uri.host}"
return f"Slow response from {parsed.netloc}"
raise ValueError(f"Unknown resource: {uri}")
@ -214,7 +215,7 @@ class ServerTest(Server): # pragma: no cover
# When the tool is called, send a notification to test GET stream
if name == "test_tool_with_standalone_notification":
await ctx.session.send_resource_updated(uri=AnyUrl("http://test_resource"))
await ctx.session.send_resource_updated(uri="http://test_resource")
return [TextContent(type="text", text=f"Called {name}")]
elif name == "long_running_with_checkpoints":
@ -368,7 +369,7 @@ class ServerTest(Server): # pragma: no cover
elif name == "tool_with_standalone_stream_close":
# Test for GET stream reconnection
# 1. Send unsolicited notification via GET stream (no related_request_id)
await ctx.session.send_resource_updated(uri=AnyUrl("http://notification_1"))
await ctx.session.send_resource_updated(uri="http://notification_1")
# Small delay to ensure notification is flushed before closing
await anyio.sleep(0.1)
@ -381,7 +382,7 @@ class ServerTest(Server): # pragma: no cover
await anyio.sleep(1.5)
# 4. Send another notification on the new GET stream connection
await ctx.session.send_resource_updated(uri=AnyUrl("http://notification_2"))
await ctx.session.send_resource_updated(uri="http://notification_2")
return [TextContent(type="text", text="Standalone stream close test done")]
@ -1009,9 +1010,9 @@ async def test_streamable_http_client_basic_connection(basic_server: None, basic
@pytest.mark.anyio
async def test_streamable_http_client_resource_read(initialized_client_session: ClientSession):
"""Test client resource read functionality."""
response = await initialized_client_session.read_resource(uri=AnyUrl("foobar://test-resource"))
response = await initialized_client_session.read_resource(uri="foobar://test-resource")
assert len(response.contents) == 1
assert response.contents[0].uri == AnyUrl("foobar://test-resource")
assert response.contents[0].uri == "foobar://test-resource"
assert isinstance(response.contents[0], TextResourceContents)
assert response.contents[0].text == "Read test-resource"
@ -1035,7 +1036,7 @@ async def test_streamable_http_client_tool_invocation(initialized_client_session
async def test_streamable_http_client_error_handling(initialized_client_session: ClientSession):
"""Test error handling in client."""
with pytest.raises(McpError) as exc_info:
await initialized_client_session.read_resource(uri=AnyUrl("unknown://test-error"))
await initialized_client_session.read_resource(uri="unknown://test-error")
assert exc_info.value.error.code == 0
assert "Unknown resource: unknown://test-error" in exc_info.value.error.message
@ -1061,7 +1062,7 @@ async def test_streamable_http_client_session_persistence(basic_server: None, ba
assert len(tools.tools) == 10
# Read a resource
resource = await session.read_resource(uri=AnyUrl("foobar://test-persist"))
resource = await session.read_resource(uri="foobar://test-persist")
assert isinstance(resource.contents[0], TextResourceContents) is True
content = resource.contents[0]
assert isinstance(content, TextResourceContents)
@ -1130,7 +1131,7 @@ async def test_streamable_http_client_get_stream(basic_server: None, basic_serve
resource_update_found = False
for notif in notifications_received:
if isinstance(notif.root, types.ResourceUpdatedNotification): # pragma: no branch
assert str(notif.root.params.uri) == "http://test_resource/"
assert str(notif.root.params.uri) == "http://test_resource"
resource_update_found = True
assert resource_update_found, "ResourceUpdatedNotification not received via GET stream"
@ -2235,10 +2236,10 @@ async def test_standalone_get_stream_reconnection(
assert result.content[0].text == "Standalone stream close test done"
# Verify both notifications were received
assert "http://notification_1/" in received_notifications, (
assert "http://notification_1" in received_notifications, (
f"Should receive notification 1 (sent before GET stream close), got: {received_notifications}"
)
assert "http://notification_2/" in received_notifications, (
assert "http://notification_2" in received_notifications, (
f"Should receive notification 2 after reconnect, got: {received_notifications}"
)

View file

@ -49,13 +49,16 @@ class ServerTest(Server): # pragma: no cover
super().__init__(SERVER_NAME)
@self.read_resource()
async def handle_read_resource(uri: AnyUrl) -> str | bytes:
if uri.scheme == "foobar":
return f"Read {uri.host}"
elif uri.scheme == "slow":
async def handle_read_resource(uri: str) -> str | bytes:
from urllib.parse import urlparse
parsed = urlparse(uri)
if parsed.scheme == "foobar":
return f"Read {parsed.netloc}"
elif parsed.scheme == "slow":
# Simulate a slow resource
await anyio.sleep(2.0)
return f"Slow response from {uri.host}"
return f"Slow response from {parsed.netloc}"
raise McpError(error=ErrorData(code=404, message="OOPS! no resource with that URI was found"))