55import  json 
66from  pathlib  import  Path 
77
8- import  anyio 
9- import  anyio .to_thread 
108import  httpx 
119import  pydantic .json 
10+ from  anyio  import  Path  as  AsyncPath 
1211from  pydantic  import  Field , ValidationInfo 
12+ from  typing_extensions  import  override 
1313
1414from  fastmcp .exceptions  import  ResourceError 
1515from  fastmcp .resources .resource  import  Resource 
@@ -54,6 +54,10 @@ class FileResource(Resource):
5454        description = "MIME type of the resource content" ,
5555    )
5656
57+     @property  
58+     def  _async_path (self ) ->  AsyncPath :
59+         return  AsyncPath (self .path )
60+ 
5761    @pydantic .field_validator ("path" ) 
5862    @classmethod  
5963    def  validate_absolute_path (cls , path : Path ) ->  Path :
@@ -71,12 +75,13 @@ def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> boo
7175        mime_type  =  info .data .get ("mime_type" , "text/plain" )
7276        return  not  mime_type .startswith ("text/" )
7377
78+     @override  
7479    async  def  read (self ) ->  str  |  bytes :
7580        """Read the file content.""" 
7681        try :
7782            if  self .is_binary :
78-                 return  await  anyio . to_thread . run_sync ( self .path .read_bytes )
79-             return  await  anyio . to_thread . run_sync ( self .path .read_text )
83+                 return  await  self ._async_path .read_bytes ( )
84+             return  await  self ._async_path .read_text ( )
8085        except  Exception  as  e :
8186            raise  ResourceError (f"Error reading file { self .path }  ) from  e 
8287
@@ -89,11 +94,12 @@ class HttpResource(Resource):
8994        default = "application/json" , description = "MIME type of the resource content" 
9095    )
9196
97+     @override  
9298    async  def  read (self ) ->  str  |  bytes :
9399        """Read the HTTP content.""" 
94100        async  with  httpx .AsyncClient () as  client :
95101            response  =  await  client .get (self .url )
96-             response .raise_for_status ()
102+             _   =   response .raise_for_status ()
97103            return  response .text 
98104
99105
@@ -111,6 +117,10 @@ class DirectoryResource(Resource):
111117        default = "application/json" , description = "MIME type of the resource content" 
112118    )
113119
120+     @property  
121+     def  _async_path (self ) ->  AsyncPath :
122+         return  AsyncPath (self .path )
123+ 
114124    @pydantic .field_validator ("path" ) 
115125    @classmethod  
116126    def  validate_absolute_path (cls , path : Path ) ->  Path :
@@ -119,33 +129,29 @@ def validate_absolute_path(cls, path: Path) -> Path:
119129            raise  ValueError ("Path must be absolute" )
120130        return  path 
121131
122-     def  list_files (self ) ->  list [Path ]:
132+     async   def  list_files (self ) ->  list [Path ]:
123133        """List files in the directory.""" 
124-         if  not  self .path .exists ():
134+         if  not  await   self ._async_path .exists ():
125135            raise  FileNotFoundError (f"Directory not found: { self .path }  )
126-         if  not  self .path .is_dir ():
136+         if  not  await   self ._async_path .is_dir ():
127137            raise  NotADirectoryError (f"Not a directory: { self .path }  )
128138
139+         pattern  =  self .pattern  or  "*" 
140+ 
141+         glob_fn  =  self ._async_path .rglob  if  self .recursive  else  self ._async_path .glob 
129142        try :
130-             if  self .pattern :
131-                 return  (
132-                     list (self .path .glob (self .pattern ))
133-                     if  not  self .recursive 
134-                     else  list (self .path .rglob (self .pattern ))
135-                 )
136-             return  (
137-                 list (self .path .glob ("*" ))
138-                 if  not  self .recursive 
139-                 else  list (self .path .rglob ("*" ))
140-             )
143+             return  [Path (p ) async  for  p  in  glob_fn (pattern ) if  await  p .is_file ()]
141144        except  Exception  as  e :
142-             raise  ResourceError (f"Error listing directory { self .path } :  { e } "  ) 
145+             raise  ResourceError (f"Error listing directory { self .path } "  )  from   e 
143146
147+     @override  
144148    async  def  read (self ) ->  str :  # Always returns JSON string 
145149        """Read the directory listing.""" 
146150        try :
147-             files  =  await  anyio .to_thread .run_sync (self .list_files )
148-             file_list  =  [str (f .relative_to (self .path )) for  f  in  files  if  f .is_file ()]
151+             files : list [Path ] =  await  self .list_files ()
152+ 
153+             file_list  =  [str (f .relative_to (self .path )) for  f  in  files ]
154+ 
149155            return  json .dumps ({"files" : file_list }, indent = 2 )
150-         except  Exception :
151-             raise  ResourceError (f"Error reading directory { self .path }  )
156+         except  Exception   as   e :
157+             raise  ResourceError (f"Error reading directory { self .path }  )  from   e 
0 commit comments