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