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  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
@@ -88,6 +95,8 @@ def from_function(
8895        if  func_name  ==  "<lambda>" :
8996            raise  ValueError ("You must provide a name for lambda functions" )
9097
98+         context_kwarg  =  find_context_parameter (fn )
99+ 
91100        # ensure the arguments are properly cast 
92101        fn  =  validate_call (fn )
93102
@@ -99,6 +108,7 @@ def from_function(
99108            mime_type = mime_type  or  "text/plain" ,
100109            fn = fn ,
101110            icons = icons ,
111+             context_kwarg = context_kwarg ,
102112        )
103113
104114
@@ -135,7 +145,7 @@ def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> boo
135145        mime_type  =  info .data .get ("mime_type" , "text/plain" )
136146        return  not  mime_type .startswith ("text/" )
137147
138-     async  def  read (self ) ->  str  |  bytes :
148+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str  |  bytes :
139149        """Read the file content.""" 
140150        try :
141151            if  self .is_binary :
@@ -151,7 +161,7 @@ class HttpResource(Resource):
151161    url : str  =  Field (description = "URL to fetch content from" )
152162    mime_type : str  =  Field (default = "application/json" , description = "MIME type of the resource content" )
153163
154-     async  def  read (self ) ->  str  |  bytes :
164+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str  |  bytes :
155165        """Read the HTTP content.""" 
156166        async  with  httpx .AsyncClient () as  client :
157167            response  =  await  client .get (self .url )
@@ -189,7 +199,7 @@ def list_files(self) -> list[Path]:
189199        except  Exception  as  e :
190200            raise  ValueError (f"Error listing directory { self .path } { e }  )
191201
192-     async  def  read (self ) ->  str :  # Always returns JSON string 
202+     async  def  read (self ,  context :  Any   |   None   =   None ) ->  str :  # Always returns JSON string 
193203        """Read the directory listing.""" 
194204        try :
195205            files  =  await  anyio .to_thread .run_sync (self .list_files )
0 commit comments