1+ """Test FastMCP resources returning ResourceContents directly."""
2+
3+ import pytest
4+ from pydantic import AnyUrl
5+
6+ from mcp .server .fastmcp import FastMCP
7+ from mcp .server .fastmcp .resources import TextResource
8+ from mcp .types import BlobResourceContents , TextResourceContents
9+
10+
11+ @pytest .mark .anyio
12+ async def test_resource_returns_text_resource_contents_directly ():
13+ """Test a custom resource that returns TextResourceContents directly."""
14+ app = FastMCP ("test" )
15+
16+ class DirectTextResource (TextResource ):
17+ """A resource that returns TextResourceContents directly."""
18+
19+ async def read (self ):
20+ # Return TextResourceContents directly instead of str
21+ return TextResourceContents (
22+ uri = self .uri ,
23+ text = "Direct TextResourceContents content" ,
24+ mimeType = "text/markdown" ,
25+ )
26+
27+ # Add the resource
28+ app .add_resource (
29+ DirectTextResource (
30+ uri = "resource://direct-text" ,
31+ name = "direct-text" ,
32+ title = "Direct Text Resource" ,
33+ description = "Returns TextResourceContents directly" ,
34+ text = "This is ignored since we override read()" ,
35+ )
36+ )
37+
38+ # Read the resource
39+ contents = await app .read_resource ("resource://direct-text" )
40+ contents_list = list (contents )
41+
42+ # Verify the result
43+ assert len (contents_list ) == 1
44+ content = contents_list [0 ]
45+ assert isinstance (content , TextResourceContents )
46+ assert content .text == "Direct TextResourceContents content"
47+ assert content .mimeType == "text/markdown"
48+ assert str (content .uri ) == "resource://direct-text"
49+
50+
51+ @pytest .mark .anyio
52+ async def test_resource_returns_blob_resource_contents_directly ():
53+ """Test a custom resource that returns BlobResourceContents directly."""
54+ app = FastMCP ("test" )
55+
56+ class DirectBlobResource (TextResource ):
57+ """A resource that returns BlobResourceContents directly."""
58+
59+ async def read (self ):
60+ # Return BlobResourceContents directly
61+ return BlobResourceContents (
62+ uri = self .uri ,
63+ blob = "SGVsbG8gRmFzdE1DUA==" , # "Hello FastMCP" in base64
64+ mimeType = "application/pdf" ,
65+ )
66+
67+ # Add the resource
68+ app .add_resource (
69+ DirectBlobResource (
70+ uri = "resource://direct-blob" ,
71+ name = "direct-blob" ,
72+ title = "Direct Blob Resource" ,
73+ description = "Returns BlobResourceContents directly" ,
74+ text = "This is ignored since we override read()" ,
75+ )
76+ )
77+
78+ # Read the resource
79+ contents = await app .read_resource ("resource://direct-blob" )
80+ contents_list = list (contents )
81+
82+ # Verify the result
83+ assert len (contents_list ) == 1
84+ content = contents_list [0 ]
85+ assert isinstance (content , BlobResourceContents )
86+ assert content .blob == "SGVsbG8gRmFzdE1DUA=="
87+ assert content .mimeType == "application/pdf"
88+ assert str (content .uri ) == "resource://direct-blob"
89+
90+
91+ @pytest .mark .anyio
92+ async def test_function_resource_returns_resource_contents ():
93+ """Test function resource returning ResourceContents directly."""
94+ app = FastMCP ("test" )
95+
96+ @app .resource ("resource://function-text-contents" )
97+ async def get_text_contents () -> TextResourceContents :
98+ """Return TextResourceContents directly from function resource."""
99+ return TextResourceContents (
100+ uri = AnyUrl ("resource://function-text-contents" ),
101+ text = "Function returned TextResourceContents" ,
102+ mimeType = "text/x-python" ,
103+ )
104+
105+ @app .resource ("resource://function-blob-contents" )
106+ def get_blob_contents () -> BlobResourceContents :
107+ """Return BlobResourceContents directly from function resource."""
108+ return BlobResourceContents (
109+ uri = AnyUrl ("resource://function-blob-contents" ),
110+ blob = "RnVuY3Rpb24gYmxvYg==" , # "Function blob" in base64
111+ mimeType = "image/png" ,
112+ )
113+
114+ # Read text resource
115+ text_contents = await app .read_resource ("resource://function-text-contents" )
116+ text_list = list (text_contents )
117+ assert len (text_list ) == 1
118+ text_content = text_list [0 ]
119+ assert isinstance (text_content , TextResourceContents )
120+ assert text_content .text == "Function returned TextResourceContents"
121+ assert text_content .mimeType == "text/x-python"
122+
123+ # Read blob resource
124+ blob_contents = await app .read_resource ("resource://function-blob-contents" )
125+ blob_list = list (blob_contents )
126+ assert len (blob_list ) == 1
127+ blob_content = blob_list [0 ]
128+ assert isinstance (blob_content , BlobResourceContents )
129+ assert blob_content .blob == "RnVuY3Rpb24gYmxvYg=="
130+ assert blob_content .mimeType == "image/png"
131+
132+
133+ @pytest .mark .anyio
134+ async def test_mixed_traditional_and_direct_resources ():
135+ """Test server with both traditional and direct ResourceContents resources."""
136+ app = FastMCP ("test" )
137+
138+ # Traditional string resource
139+ @app .resource ("resource://traditional" )
140+ def traditional_resource () -> str :
141+ return "Traditional string content"
142+
143+ # Direct ResourceContents resource
144+ @app .resource ("resource://direct" )
145+ def direct_resource () -> TextResourceContents :
146+ return TextResourceContents (
147+ uri = AnyUrl ("resource://direct" ),
148+ text = "Direct ResourceContents content" ,
149+ mimeType = "text/html" ,
150+ )
151+
152+ # Read traditional resource (will be wrapped)
153+ trad_contents = await app .read_resource ("resource://traditional" )
154+ trad_list = list (trad_contents )
155+ assert len (trad_list ) == 1
156+ # The content type might be ReadResourceContents, but we're checking the behavior
157+
158+ # Read direct ResourceContents
159+ direct_contents = await app .read_resource ("resource://direct" )
160+ direct_list = list (direct_contents )
161+ assert len (direct_list ) == 1
162+ direct_content = direct_list [0 ]
163+ assert isinstance (direct_content , TextResourceContents )
164+ assert direct_content .text == "Direct ResourceContents content"
165+ assert direct_content .mimeType == "text/html"
166+
167+
168+ @pytest .mark .anyio
169+ async def test_resource_template_returns_resource_contents ():
170+ """Test resource template returning ResourceContents directly."""
171+ app = FastMCP ("test" )
172+
173+ @app .resource ("resource://{category}/{item}" )
174+ async def get_item_contents (category : str , item : str ) -> TextResourceContents :
175+ """Return TextResourceContents for template resource."""
176+ return TextResourceContents (
177+ uri = AnyUrl (f"resource://{ category } /{ item } " ),
178+ text = f"Content for { item } in { category } " ,
179+ mimeType = "text/plain" ,
180+ )
181+
182+ # Read templated resource
183+ contents = await app .read_resource ("resource://books/python" )
184+ contents_list = list (contents )
185+ assert len (contents_list ) == 1
186+ content = contents_list [0 ]
187+ assert isinstance (content , TextResourceContents )
188+ assert content .text == "Content for python in books"
189+ assert content .mimeType == "text/plain"
190+ assert str (content .uri ) == "resource://books/python"
0 commit comments