1414from  pydantic  import  AnyUrl , Field , ValidationInfo , validate_call 
1515
1616from  mcp .server .fastmcp .resources .base  import  Resource 
17+ from  mcp .server .fastmcp .utilities .context_injection  import  find_context_parameter 
1718from  mcp .types  import  Annotations , Icon 
1819
1920
@@ -22,7 +23,7 @@ class TextResource(Resource):
2223
2324    text : str  =  Field (description = "Text content of the resource" )
2425
25-     async  def  read (self ) ->  str :
26+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str :
2627        """Read the text content.""" 
2728        return  self .text 
2829
@@ -32,7 +33,7 @@ class BinaryResource(Resource):
3233
3334    data : bytes  =  Field (description = "Binary content of the resource" )
3435
35-     async  def  read (self ) ->  bytes :
36+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  bytes :
3637        """Read the binary content.""" 
3738        return  self .data 
3839
@@ -51,24 +52,30 @@ class FunctionResource(Resource):
5152    """ 
5253
5354    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 
5462
55-     async  def  read (self ) ->  str  |  bytes :
56-         """Read the resource by calling the wrapped function.""" 
5763        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 )
7066            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 ))
7279        except  Exception  as  e :
7380            raise  ValueError (f"Error reading resource { self .uri } { e }  )
7481
@@ -89,6 +96,8 @@ def from_function(
8996        if  func_name  ==  "<lambda>" :
9097            raise  ValueError ("You must provide a name for lambda functions" )
9198
99+         context_kwarg  =  find_context_parameter (fn )
100+ 
92101        # ensure the arguments are properly cast 
93102        fn  =  validate_call (fn )
94103
@@ -100,6 +109,7 @@ def from_function(
100109            mime_type = mime_type  or  "text/plain" ,
101110            fn = fn ,
102111            icons = icons ,
112+             context_kwarg = context_kwarg ,
103113            annotations = annotations ,
104114        )
105115
@@ -137,7 +147,7 @@ def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> boo
137147        mime_type  =  info .data .get ("mime_type" , "text/plain" )
138148        return  not  mime_type .startswith ("text/" )
139149
140-     async  def  read (self ) ->  str  |  bytes :
150+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str  |  bytes :
141151        """Read the file content.""" 
142152        try :
143153            if  self .is_binary :
@@ -153,7 +163,7 @@ class HttpResource(Resource):
153163    url : str  =  Field (description = "URL to fetch content from" )
154164    mime_type : str  =  Field (default = "application/json" , description = "MIME type of the resource content" )
155165
156-     async  def  read (self ) ->  str  |  bytes :
166+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str  |  bytes :
157167        """Read the HTTP content.""" 
158168        async  with  httpx .AsyncClient () as  client :
159169            response  =  await  client .get (self .url )
@@ -191,7 +201,7 @@ def list_files(self) -> list[Path]:
191201        except  Exception  as  e :
192202            raise  ValueError (f"Error listing directory { self .path } { e }  )
193203
194-     async  def  read (self ) ->  str :  # Always returns JSON string 
204+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str :  # Always returns JSON string 
195205        """Read the directory listing.""" 
196206        try :
197207            files  =  await  anyio .to_thread .run_sync (self .list_files )
0 commit comments