Skip to content

Commit 0ddf851

Browse files
fix: Resolve pyright type errors in ResourceContents tests
Fixed type narrowing issues in test files that were accessing attributes on union types without proper isinstance checks. The FastMCP read_resource method returns ReadResourceContents | TextResourceContents | BlobResourceContents, requiring explicit type checking before accessing type-specific attributes. Changes: - Added proper type narrowing with isinstance() checks in all affected tests - Fixed incorrect Resource base class usage in test files - Corrected AnyUrl constructor usage in resource creation - Updated lowlevel example to avoid delegation conflicts All tests pass and pyright reports 0 errors. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bd26046 commit 0ddf851

File tree

5 files changed

+72
-16
lines changed

5 files changed

+72
-16
lines changed

examples/snippets/servers/lowlevel/resource_contents_direct.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,14 @@ async def read_legacy_resource(
245245
)
246246
]
247247

248-
# Delegate to the new handler for other resources
249-
return await read_resource(uri)
248+
# For other resources, return a simple not found message
249+
return [
250+
types.TextResourceContents(
251+
uri=uri,
252+
text=f"Resource not found: {uri}",
253+
mimeType="text/plain",
254+
)
255+
]
250256

251257

252258
async def main():

tests/issues/test_141_resource_templates.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,21 @@ def get_user_profile_missing(user_id: str) -> str:
5353
result = await mcp.read_resource("resource://users/123/posts/456")
5454
result_list = list(result)
5555
assert len(result_list) == 1
56-
assert result_list[0].content == "Post 456 by user 123"
57-
assert result_list[0].mime_type == "text/plain"
56+
content = result_list[0]
57+
# Since this is a string resource, it should be wrapped as ReadResourceContents
58+
from mcp.server.lowlevel.server import ReadResourceContents
59+
from mcp.types import TextResourceContents
60+
61+
if isinstance(content, ReadResourceContents):
62+
assert content.content == "Post 456 by user 123"
63+
assert content.mime_type == "text/plain"
64+
elif isinstance(content, TextResourceContents):
65+
# If it's TextResourceContents (direct return)
66+
assert content.text == "Post 456 by user 123"
67+
assert content.mimeType == "text/plain"
68+
else:
69+
# Should not happen for string resources
70+
raise AssertionError(f"Unexpected content type: {type(content)}")
5871

5972
# Verify invalid parameters raise error
6073
with pytest.raises(ValueError, match="Unknown resource"):

tests/server/fastmcp/resources/test_resource_contents_direct.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pydantic import AnyUrl
55

66
from mcp.server.fastmcp import FastMCP
7-
from mcp.server.fastmcp.resources import TextResource
7+
from mcp.server.fastmcp.resources import Resource
88
from mcp.types import BlobResourceContents, TextResourceContents
99

1010

@@ -13,7 +13,7 @@ async def test_resource_returns_text_resource_contents_directly():
1313
"""Test a custom resource that returns TextResourceContents directly."""
1414
app = FastMCP("test")
1515

16-
class DirectTextResource(TextResource):
16+
class DirectTextResource(Resource):
1717
"""A resource that returns TextResourceContents directly."""
1818

1919
async def read(self):
@@ -27,11 +27,10 @@ async def read(self):
2727
# Add the resource
2828
app.add_resource(
2929
DirectTextResource(
30-
uri="resource://direct-text",
30+
uri=AnyUrl("resource://direct-text"),
3131
name="direct-text",
3232
title="Direct Text Resource",
3333
description="Returns TextResourceContents directly",
34-
text="This is ignored since we override read()",
3534
)
3635
)
3736

@@ -53,7 +52,7 @@ async def test_resource_returns_blob_resource_contents_directly():
5352
"""Test a custom resource that returns BlobResourceContents directly."""
5453
app = FastMCP("test")
5554

56-
class DirectBlobResource(TextResource):
55+
class DirectBlobResource(Resource):
5756
"""A resource that returns BlobResourceContents directly."""
5857

5958
async def read(self):
@@ -67,11 +66,10 @@ async def read(self):
6766
# Add the resource
6867
app.add_resource(
6968
DirectBlobResource(
70-
uri="resource://direct-blob",
69+
uri=AnyUrl("resource://direct-blob"),
7170
name="direct-blob",
7271
title="Direct Blob Resource",
7372
description="Returns BlobResourceContents directly",
74-
text="This is ignored since we override read()",
7573
)
7674
)
7775

tests/server/fastmcp/servers/test_file_server.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,19 @@ async def test_read_resource_dir(mcp: FastMCP):
9292
res_list = list(res_iter)
9393
assert len(res_list) == 1
9494
res = res_list[0]
95-
assert res.mime_type == "text/plain"
9695

97-
files = json.loads(res.content)
96+
# Handle union type properly
97+
from mcp.server.lowlevel.server import ReadResourceContents
98+
from mcp.types import TextResourceContents
99+
100+
if isinstance(res, ReadResourceContents):
101+
assert res.mime_type == "text/plain"
102+
files = json.loads(res.content)
103+
elif isinstance(res, TextResourceContents):
104+
assert res.mimeType == "text/plain"
105+
files = json.loads(res.text)
106+
else:
107+
raise AssertionError(f"Unexpected content type: {type(res)}")
98108

99109
assert sorted([Path(f).name for f in files]) == [
100110
"config.json",
@@ -109,7 +119,17 @@ async def test_read_resource_file(mcp: FastMCP):
109119
res_list = list(res_iter)
110120
assert len(res_list) == 1
111121
res = res_list[0]
112-
assert res.content == "print('hello world')"
122+
123+
# Handle union type properly
124+
from mcp.server.lowlevel.server import ReadResourceContents
125+
from mcp.types import TextResourceContents
126+
127+
if isinstance(res, ReadResourceContents):
128+
assert res.content == "print('hello world')"
129+
elif isinstance(res, TextResourceContents):
130+
assert res.text == "print('hello world')"
131+
else:
132+
raise AssertionError(f"Unexpected content type: {type(res)}")
113133

114134

115135
@pytest.mark.anyio
@@ -125,4 +145,14 @@ async def test_delete_file_and_check_resources(mcp: FastMCP, test_dir: Path):
125145
res_list = list(res_iter)
126146
assert len(res_list) == 1
127147
res = res_list[0]
128-
assert res.content == "File not found"
148+
149+
# Handle union type properly
150+
from mcp.server.lowlevel.server import ReadResourceContents
151+
from mcp.types import TextResourceContents
152+
153+
if isinstance(res, ReadResourceContents):
154+
assert res.content == "File not found"
155+
elif isinstance(res, TextResourceContents):
156+
assert res.text == "File not found"
157+
else:
158+
raise AssertionError(f"Unexpected content type: {type(res)}")

tests/server/fastmcp/test_server.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,16 @@ async def tool_with_resource(ctx: Context[ServerSession, None]) -> str:
10351035
r_list = list(r_iter)
10361036
assert len(r_list) == 1
10371037
r = r_list[0]
1038-
return f"Read resource: {r.content} with mime type {r.mime_type}"
1038+
# Handle union type properly
1039+
from mcp.server.lowlevel.server import ReadResourceContents
1040+
from mcp.types import TextResourceContents
1041+
1042+
if isinstance(r, ReadResourceContents):
1043+
return f"Read resource: {r.content} with mime type {r.mime_type}"
1044+
elif isinstance(r, TextResourceContents):
1045+
return f"Read resource: {r.text} with mime type {r.mimeType}"
1046+
else:
1047+
raise AssertionError(f"Unexpected content type: {type(r)}")
10391048

10401049
async with client_session(mcp._mcp_server) as client:
10411050
result = await client.call_tool("tool_with_resource", {})

0 commit comments

Comments
 (0)