|
16 | 16 | from mcp_types import Annotations, Icon, InputRequiredResult |
17 | 17 | from pydantic import Field, validate_call |
18 | 18 |
|
19 | | -from mcp.server.mcpserver.exceptions import ResourceError, UnexpectedResourceError |
20 | 19 | from mcp.server.mcpserver.resources.base import Resource |
21 | 20 | from mcp.shared._callable_inspection import is_async_callable |
22 | | -from mcp.shared.exceptions import MCPError |
23 | 21 |
|
24 | 22 | # `application/*` types that are textual but predate the `+json`/`+xml` |
25 | 23 | # structured-syntax suffixes, so the suffix rule below can't catch them. |
@@ -80,41 +78,29 @@ class FunctionResource(Resource): |
80 | 78 | fn: Callable[[], Any] = Field(exclude=True) |
81 | 79 |
|
82 | 80 | async def read(self) -> str | bytes: |
83 | | - """Read the resource by calling the wrapped function. |
84 | | -
|
85 | | - Raises: |
86 | | - UnexpectedResourceError: If the function raises anything other than |
87 | | - `ResourceError` or `MCPError`. `__cause__` is the original exception. |
88 | | - """ |
89 | | - try: |
90 | | - fn = self.fn |
91 | | - if is_async_callable(fn): |
92 | | - result = await fn() |
93 | | - else: |
94 | | - result = await anyio.to_thread.run_sync(self.fn) |
95 | | - |
96 | | - if isinstance(result, InputRequiredResult): |
97 | | - # A static resource function can never read the retry's |
98 | | - # input_responses (it takes no Context), so this can only be a |
99 | | - # mistake — reject it instead of JSON-dumping it as content. |
100 | | - raise ValueError( |
101 | | - "static resources cannot return InputRequiredResult; only resource " |
102 | | - "template functions participate in the multi-round-trip flow" |
103 | | - ) |
104 | | - if isinstance(result, Resource): # pragma: no cover |
105 | | - return await result.read() |
106 | | - elif isinstance(result, bytes): |
107 | | - return result |
108 | | - elif isinstance(result, str): |
109 | | - return result |
110 | | - else: |
111 | | - return pydantic_core.to_json(result, fallback=str, indent=2).decode() |
112 | | - except (MCPError, ResourceError): |
113 | | - raise |
114 | | - except Exception as exc: |
115 | | - # Name only the URI: the original text is withheld from the client, and |
116 | | - # the server logs the traceback from `__cause__`. |
117 | | - raise UnexpectedResourceError(f"Error reading resource {self.uri}") from exc |
| 81 | + """Read the resource by calling the wrapped function.""" |
| 82 | + fn = self.fn |
| 83 | + if is_async_callable(fn): |
| 84 | + result = await fn() |
| 85 | + else: |
| 86 | + result = await anyio.to_thread.run_sync(self.fn) |
| 87 | + |
| 88 | + if isinstance(result, InputRequiredResult): |
| 89 | + # A static resource function can never read the retry's |
| 90 | + # input_responses (it takes no Context), so this can only be a |
| 91 | + # mistake — reject it instead of JSON-dumping it as content. |
| 92 | + raise ValueError( |
| 93 | + "static resources cannot return InputRequiredResult; only resource " |
| 94 | + "template functions participate in the multi-round-trip flow" |
| 95 | + ) |
| 96 | + if isinstance(result, Resource): # pragma: no cover |
| 97 | + return await result.read() |
| 98 | + elif isinstance(result, bytes): |
| 99 | + return result |
| 100 | + elif isinstance(result, str): |
| 101 | + return result |
| 102 | + else: |
| 103 | + return pydantic_core.to_json(result, fallback=str, indent=2).decode() |
118 | 104 |
|
119 | 105 | @classmethod |
120 | 106 | def from_function( |
@@ -191,12 +177,9 @@ def validate_text_encoding(cls, encoding: str | None) -> str | None: |
191 | 177 |
|
192 | 178 | async def read(self) -> str | bytes: |
193 | 179 | """Read the file content.""" |
194 | | - try: |
195 | | - if self.encoding is None: |
196 | | - return await anyio.to_thread.run_sync(self.path.read_bytes) |
197 | | - return await anyio.to_thread.run_sync(partial(self.path.read_text, encoding=self.encoding)) |
198 | | - except Exception as exc: |
199 | | - raise UnexpectedResourceError(f"Error reading resource {self.uri}") from exc |
| 180 | + if self.encoding is None: |
| 181 | + return await anyio.to_thread.run_sync(self.path.read_bytes) |
| 182 | + return await anyio.to_thread.run_sync(partial(self.path.read_text, encoding=self.encoding)) |
200 | 183 |
|
201 | 184 |
|
202 | 185 | class HttpResource(Resource): |
@@ -236,18 +219,12 @@ def list_files(self) -> list[Path]: # pragma: no cover |
236 | 219 | if not self.path.is_dir(): |
237 | 220 | raise NotADirectoryError(f"Not a directory: {self.path}") |
238 | 221 |
|
239 | | - try: |
240 | | - if self.pattern: |
241 | | - return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern)) |
242 | | - return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*")) |
243 | | - except Exception as exc: |
244 | | - raise ValueError(f"Error listing directory {self.path}: {exc}") from exc |
| 222 | + if self.pattern: |
| 223 | + return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern)) |
| 224 | + return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*")) |
245 | 225 |
|
246 | 226 | async def read(self) -> str: # Always returns JSON string # pragma: no cover |
247 | 227 | """Read the directory listing.""" |
248 | | - try: |
249 | | - files = await anyio.to_thread.run_sync(self.list_files) |
250 | | - file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()] |
251 | | - return json.dumps({"files": file_list}, indent=2) |
252 | | - except Exception as exc: |
253 | | - raise UnexpectedResourceError(f"Error reading resource {self.uri}") from exc |
| 228 | + files = await anyio.to_thread.run_sync(self.list_files) |
| 229 | + file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()] |
| 230 | + return json.dumps({"files": file_list}, indent=2) |
0 commit comments