14
14
from pydantic import AnyUrl , Field , ValidationInfo , validate_call
15
15
16
16
from mcp .server .fastmcp .resources .base import Resource
17
+ from mcp .server .fastmcp .utilities .context_injection import find_context_parameter
17
18
from mcp .types import Icon
18
19
19
20
@@ -22,7 +23,7 @@ class TextResource(Resource):
22
23
23
24
text : str = Field (description = "Text content of the resource" )
24
25
25
- async def read (self ) -> str :
26
+ async def read (self , context : Any | None = None ) -> str :
26
27
"""Read the text content."""
27
28
return self .text
28
29
@@ -32,7 +33,7 @@ class BinaryResource(Resource):
32
33
33
34
data : bytes = Field (description = "Binary content of the resource" )
34
35
35
- async def read (self ) -> bytes :
36
+ async def read (self , context : Any | None = None ) -> bytes :
36
37
"""Read the binary content."""
37
38
return self .data
38
39
@@ -51,24 +52,30 @@ class FunctionResource(Resource):
51
52
"""
52
53
53
54
fn : Callable [[], Any ] = Field (exclude = True )
55
+ context_kwarg : str | None = Field (None , exclude = True )
56
+
57
+ async def read (self , context : Any | None = None ) -> str | bytes :
58
+ """Read the resource content by calling the function."""
59
+ args = {}
60
+ if self .context_kwarg :
61
+ args [self .context_kwarg ] = context
54
62
55
- async def read (self ) -> str | bytes :
56
- """Read the resource by calling the wrapped function."""
57
63
try :
58
- # Call the function first to see if it returns a coroutine
59
- result = self .fn ()
60
- # If it's a coroutine, await it
61
- if inspect .iscoroutine (result ):
62
- result = await result
63
-
64
- if isinstance (result , Resource ):
65
- return await result .read ()
66
- elif isinstance (result , bytes ):
67
- return result
68
- elif isinstance (result , str ):
69
- return result
64
+ if inspect .iscoroutinefunction (self .fn ):
65
+ result = await self .fn (** args )
70
66
else :
71
- return pydantic_core .to_json (result , fallback = str , indent = 2 ).decode ()
67
+ result = self .fn (** args )
68
+
69
+ if isinstance (result , str | bytes ):
70
+ return result
71
+ if isinstance (result , pydantic .BaseModel ):
72
+ return result .model_dump_json (indent = 2 )
73
+
74
+ # For other types, convert to a JSON string
75
+ try :
76
+ return json .dumps (pydantic_core .to_jsonable_python (result ))
77
+ except pydantic_core .PydanticSerializationError :
78
+ return json .dumps (str (result ))
72
79
except Exception as e :
73
80
raise ValueError (f"Error reading resource { self .uri } : { e } " )
74
81
@@ -88,6 +95,8 @@ def from_function(
88
95
if func_name == "<lambda>" :
89
96
raise ValueError ("You must provide a name for lambda functions" )
90
97
98
+ context_kwarg = find_context_parameter (fn )
99
+
91
100
# ensure the arguments are properly cast
92
101
fn = validate_call (fn )
93
102
@@ -99,6 +108,7 @@ def from_function(
99
108
mime_type = mime_type or "text/plain" ,
100
109
fn = fn ,
101
110
icons = icons ,
111
+ context_kwarg = context_kwarg ,
102
112
)
103
113
104
114
@@ -135,7 +145,7 @@ def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> boo
135
145
mime_type = info .data .get ("mime_type" , "text/plain" )
136
146
return not mime_type .startswith ("text/" )
137
147
138
- async def read (self ) -> str | bytes :
148
+ async def read (self , context : Any | None = None ) -> str | bytes :
139
149
"""Read the file content."""
140
150
try :
141
151
if self .is_binary :
@@ -151,7 +161,7 @@ class HttpResource(Resource):
151
161
url : str = Field (description = "URL to fetch content from" )
152
162
mime_type : str = Field (default = "application/json" , description = "MIME type of the resource content" )
153
163
154
- async def read (self ) -> str | bytes :
164
+ async def read (self , context : Any | None = None ) -> str | bytes :
155
165
"""Read the HTTP content."""
156
166
async with httpx .AsyncClient () as client :
157
167
response = await client .get (self .url )
@@ -189,7 +199,7 @@ def list_files(self) -> list[Path]:
189
199
except Exception as e :
190
200
raise ValueError (f"Error listing directory { self .path } : { e } " )
191
201
192
- async def read (self ) -> str : # Always returns JSON string
202
+ async def read (self , context : Any | None = None ) -> str : # Always returns JSON string
193
203
"""Read the directory listing."""
194
204
try :
195
205
files = await anyio .to_thread .run_sync (self .list_files )
0 commit comments