Skip to content

Commit f1c3531

Browse files
fennb0Delta
authored andcommitted
feat: add resource annotations support to FastMCP (modelcontextprotocol#1468)
1 parent 8ce0cf9 commit f1c3531

File tree

1 file changed

+16
-8
lines changed
  • src/mcp/server/fastmcp/resources

1 file changed

+16
-8
lines changed

src/mcp/server/fastmcp/resources/types.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class TextResource(Resource):
2525

2626
async def read(self, context: Any | None = None) -> str:
2727
"""Read the text content."""
28-
return self.text
28+
return self.text # pragma: no cover
2929

3030

3131
class BinaryResource(Resource):
@@ -35,7 +35,7 @@ class BinaryResource(Resource):
3535

3636
async def read(self, context: Any | None = None) -> bytes:
3737
"""Read the binary content."""
38-
return self.data
38+
return self.data # pragma: no cover
3939

4040

4141
class FunctionResource(Resource):
@@ -66,6 +66,14 @@ async def read(self, context: Any | None = None) -> str | bytes:
6666
else:
6767
result = self.fn(**args)
6868

69+
# Support cases where a sync function returns a coroutine
70+
if inspect.iscoroutine(result):
71+
result = await result
72+
73+
# Support returning a Resource instance (recursive read)
74+
if isinstance(result, Resource):
75+
return await result.read(context)
76+
6977
if isinstance(result, str | bytes):
7078
return result
7179
if isinstance(result, pydantic.BaseModel):
@@ -93,7 +101,7 @@ def from_function(
93101
) -> "FunctionResource":
94102
"""Create a FunctionResource from a function."""
95103
func_name = name or fn.__name__
96-
if func_name == "<lambda>":
104+
if func_name == "<lambda>": # pragma: no cover
97105
raise ValueError("You must provide a name for lambda functions")
98106

99107
context_kwarg = find_context_parameter(fn)
@@ -132,7 +140,7 @@ class FileResource(Resource):
132140

133141
@pydantic.field_validator("path")
134142
@classmethod
135-
def validate_absolute_path(cls, path: Path) -> Path:
143+
def validate_absolute_path(cls, path: Path) -> Path: # pragma: no cover
136144
"""Ensure path is absolute."""
137145
if not path.is_absolute():
138146
raise ValueError("Path must be absolute")
@@ -181,13 +189,13 @@ class DirectoryResource(Resource):
181189

182190
@pydantic.field_validator("path")
183191
@classmethod
184-
def validate_absolute_path(cls, path: Path) -> Path:
192+
def validate_absolute_path(cls, path: Path) -> Path: # pragma: no cover
185193
"""Ensure path is absolute."""
186194
if not path.is_absolute():
187195
raise ValueError("Path must be absolute")
188196
return path
189197

190-
def list_files(self) -> list[Path]:
198+
def list_files(self) -> list[Path]: # pragma: no cover
191199
"""List files in the directory."""
192200
if not self.path.exists():
193201
raise FileNotFoundError(f"Directory not found: {self.path}")
@@ -201,11 +209,11 @@ def list_files(self) -> list[Path]:
201209
except Exception as e:
202210
raise ValueError(f"Error listing directory {self.path}: {e}")
203211

204-
async def read(self, context: Any | None = None) -> str: # Always returns JSON string
212+
async def read(self, context: Any | None = None) -> str: # Always returns JSON string # pragma: no cover
205213
"""Read the directory listing."""
206214
try:
207215
files = await anyio.to_thread.run_sync(self.list_files)
208216
file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()]
209217
return json.dumps({"files": file_list}, indent=2)
210218
except Exception as e:
211-
raise ValueError(f"Error reading directory {self.path}: {e}")
219+
raise ValueError(f"Error reading directory {self.path}: {e}")

0 commit comments

Comments
 (0)