fastmcp 2.2.4__py3-none-any.whl → 2.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastmcp/__init__.py CHANGED
@@ -14,6 +14,7 @@ __all__ = [
14
14
  "FastMCP",
15
15
  "Context",
16
16
  "client",
17
+ "Client",
17
18
  "settings",
18
19
  "Image",
19
20
  ]
fastmcp/client/base.py CHANGED
@@ -1 +0,0 @@
1
-
fastmcp/client/client.py CHANGED
@@ -5,12 +5,9 @@ from typing import Any, Literal, cast, overload
5
5
 
6
6
  import mcp.types
7
7
  from mcp import ClientSession
8
- from mcp.client.session import (
9
- LoggingFnT,
10
- MessageHandlerFnT,
11
- )
12
8
  from pydantic import AnyUrl
13
9
 
10
+ from fastmcp.client.logging import LogHandler, MessageHandler
14
11
  from fastmcp.client.roots import (
15
12
  RootsHandler,
16
13
  RootsList,
@@ -22,7 +19,14 @@ from fastmcp.server import FastMCP
22
19
 
23
20
  from .transports import ClientTransport, SessionKwargs, infer_transport
24
21
 
25
- __all__ = ["Client", "RootsHandler", "RootsList"]
22
+ __all__ = [
23
+ "Client",
24
+ "RootsHandler",
25
+ "RootsList",
26
+ "LogHandler",
27
+ "MessageHandler",
28
+ "SamplingHandler",
29
+ ]
26
30
 
27
31
 
28
32
  class Client:
@@ -35,12 +39,12 @@ class Client:
35
39
 
36
40
  def __init__(
37
41
  self,
38
- transport: ClientTransport | FastMCP | AnyUrl | Path | str,
42
+ transport: ClientTransport | FastMCP | AnyUrl | Path | dict[str, Any] | str,
39
43
  # Common args
40
44
  roots: RootsList | RootsHandler | None = None,
41
45
  sampling_handler: SamplingHandler | None = None,
42
- log_handler: LoggingFnT | None = None,
43
- message_handler: MessageHandlerFnT | None = None,
46
+ log_handler: LogHandler | None = None,
47
+ message_handler: MessageHandler | None = None,
44
48
  read_timeout_seconds: datetime.timedelta | None = None,
45
49
  ):
46
50
  self.transport = infer_transport(transport)
@@ -0,0 +1,13 @@
1
+ from typing import TypeAlias
2
+
3
+ from mcp.client.session import (
4
+ LoggingFnT,
5
+ MessageHandlerFnT,
6
+ )
7
+ from mcp.types import LoggingMessageNotificationParams
8
+
9
+ LogMessage: TypeAlias = LoggingMessageNotificationParams
10
+ LogHandler: TypeAlias = LoggingFnT
11
+ MessageHandler: TypeAlias = MessageHandlerFnT
12
+
13
+ __all__ = ["LogMessage", "LogHandler", "MessageHandler"]
@@ -9,6 +9,8 @@ from mcp.shared.context import LifespanContextT, RequestContext
9
9
  from mcp.types import CreateMessageRequestParams as SamplingParams
10
10
  from mcp.types import SamplingMessage
11
11
 
12
+ __all__ = ["SamplingMessage", "SamplingParams", "MessageResult", "SamplingHandler"]
13
+
12
14
 
13
15
  class MessageResult(CreateMessageResult):
14
16
  role: mcp.types.Role = "assistant"
@@ -6,9 +6,7 @@ import shutil
6
6
  import sys
7
7
  from collections.abc import AsyncIterator
8
8
  from pathlib import Path
9
- from typing import (
10
- TypedDict,
11
- )
9
+ from typing import Any, TypedDict
12
10
 
13
11
  from exceptiongroup import BaseExceptionGroup, catch
14
12
  from mcp import ClientSession, McpError, StdioServerParameters
@@ -416,7 +414,7 @@ class FastMCPTransport(ClientTransport):
416
414
 
417
415
 
418
416
  def infer_transport(
419
- transport: ClientTransport | FastMCPServer | AnyUrl | Path | str,
417
+ transport: ClientTransport | FastMCPServer | AnyUrl | Path | dict[str, Any] | str,
420
418
  ) -> ClientTransport:
421
419
  """
422
420
  Infer the appropriate transport type from the given transport argument.
@@ -450,6 +448,41 @@ def infer_transport(
450
448
  elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
451
449
  return WSTransport(url=transport)
452
450
 
451
+ ## if the transport is a config dict
452
+ elif isinstance(transport, dict):
453
+ if "mcpServers" not in transport:
454
+ raise ValueError("Invalid transport dictionary: missing 'mcpServers' key")
455
+ else:
456
+ server = transport["mcpServers"]
457
+ if len(list(server.keys())) > 1:
458
+ raise ValueError(
459
+ "Invalid transport dictionary: multiple servers found - only one expected"
460
+ )
461
+ server_name = list(server.keys())[0]
462
+ # Stdio transport
463
+ if "command" in server[server_name] and "args" in server[server_name]:
464
+ return StdioTransport(
465
+ command=server[server_name]["command"],
466
+ args=server[server_name]["args"],
467
+ env=server[server_name].get("env", None),
468
+ cwd=server[server_name].get("cwd", None),
469
+ )
470
+
471
+ # HTTP transport
472
+ elif "url" in server:
473
+ return SSETransport(
474
+ url=server["url"],
475
+ headers=server.get("headers", None),
476
+ )
477
+
478
+ # WebSocket transport
479
+ elif "ws_url" in server:
480
+ return WSTransport(
481
+ url=server["ws_url"],
482
+ )
483
+
484
+ raise ValueError("Cannot determine transport type from dictionary")
485
+
453
486
  # the transport is an unknown type
454
487
  else:
455
488
  raise ValueError(f"Could not infer a valid transport from: {transport}")
fastmcp/prompts/prompt.py CHANGED
@@ -1,9 +1,11 @@
1
1
  """Base classes for FastMCP prompts."""
2
2
 
3
+ from __future__ import annotations as _annotations
4
+
3
5
  import inspect
4
6
  import json
5
7
  from collections.abc import Awaitable, Callable, Sequence
6
- from typing import Annotated, Any, Literal
8
+ from typing import TYPE_CHECKING, Annotated, Any, Literal
7
9
 
8
10
  import pydantic_core
9
11
  from mcp.types import EmbeddedResource, ImageContent, TextContent
@@ -13,6 +15,12 @@ from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_ca
13
15
 
14
16
  from fastmcp.utilities.types import _convert_set_defaults
15
17
 
18
+ if TYPE_CHECKING:
19
+ from mcp.server.session import ServerSessionT
20
+ from mcp.shared.context import LifespanContextT
21
+
22
+ from fastmcp.server import Context
23
+
16
24
  CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
17
25
 
18
26
 
@@ -72,6 +80,9 @@ class Prompt(BaseModel):
72
80
  None, description="Arguments that can be passed to the prompt"
73
81
  )
74
82
  fn: Callable[..., PromptResult | Awaitable[PromptResult]]
83
+ context_kwarg: str | None = Field(
84
+ None, description="Name of the kwarg that should receive context"
85
+ )
75
86
 
76
87
  @classmethod
77
88
  def from_function(
@@ -80,7 +91,8 @@ class Prompt(BaseModel):
80
91
  name: str | None = None,
81
92
  description: str | None = None,
82
93
  tags: set[str] | None = None,
83
- ) -> "Prompt":
94
+ context_kwarg: str | None = None,
95
+ ) -> Prompt:
84
96
  """Create a Prompt from a function.
85
97
 
86
98
  The function can return:
@@ -89,11 +101,24 @@ class Prompt(BaseModel):
89
101
  - A dict (converted to a message)
90
102
  - A sequence of any of the above
91
103
  """
104
+ from fastmcp import Context
105
+
92
106
  func_name = name or fn.__name__
93
107
 
94
108
  if func_name == "<lambda>":
95
109
  raise ValueError("You must provide a name for lambda functions")
96
110
 
111
+ # Auto-detect context parameter if not provided
112
+ if context_kwarg is None:
113
+ if inspect.ismethod(fn) and hasattr(fn, "__func__"):
114
+ sig = inspect.signature(fn.__func__)
115
+ else:
116
+ sig = inspect.signature(fn)
117
+ for param_name, param in sig.parameters.items():
118
+ if param.annotation is Context:
119
+ context_kwarg = param_name
120
+ break
121
+
97
122
  # Get schema from TypeAdapter - will fail if function isn't properly typed
98
123
  parameters = TypeAdapter(fn).json_schema()
99
124
 
@@ -101,6 +126,10 @@ class Prompt(BaseModel):
101
126
  arguments: list[PromptArgument] = []
102
127
  if "properties" in parameters:
103
128
  for param_name, param in parameters["properties"].items():
129
+ # Skip context parameter
130
+ if param_name == context_kwarg:
131
+ continue
132
+
104
133
  required = param_name in parameters.get("required", [])
105
134
  arguments.append(
106
135
  PromptArgument(
@@ -119,9 +148,14 @@ class Prompt(BaseModel):
119
148
  arguments=arguments,
120
149
  fn=fn,
121
150
  tags=tags or set(),
151
+ context_kwarg=context_kwarg,
122
152
  )
123
153
 
124
- async def render(self, arguments: dict[str, Any] | None = None) -> list[Message]:
154
+ async def render(
155
+ self,
156
+ arguments: dict[str, Any] | None = None,
157
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
158
+ ) -> list[Message]:
125
159
  """Render the prompt with arguments."""
126
160
  # Validate required arguments
127
161
  if self.arguments:
@@ -132,8 +166,13 @@ class Prompt(BaseModel):
132
166
  raise ValueError(f"Missing required arguments: {missing}")
133
167
 
134
168
  try:
169
+ # Prepare arguments with context
170
+ kwargs = arguments.copy() if arguments else {}
171
+ if self.context_kwarg is not None and context is not None:
172
+ kwargs[self.context_kwarg] = context
173
+
135
174
  # Call function and check if result is a coroutine
136
- result = self.fn(**(arguments or {}))
175
+ result = self.fn(**kwargs)
137
176
  if inspect.iscoroutine(result):
138
177
  result = await result
139
178
 
@@ -1,13 +1,21 @@
1
1
  """Prompt management functionality."""
2
2
 
3
+ from __future__ import annotations as _annotations
4
+
3
5
  from collections.abc import Awaitable, Callable
4
- from typing import Any
6
+ from typing import TYPE_CHECKING, Any
5
7
 
6
8
  from fastmcp.exceptions import NotFoundError
7
9
  from fastmcp.prompts.prompt import Message, Prompt, PromptResult
8
10
  from fastmcp.settings import DuplicateBehavior
9
11
  from fastmcp.utilities.logging import get_logger
10
12
 
13
+ if TYPE_CHECKING:
14
+ from mcp.server.session import ServerSessionT
15
+ from mcp.shared.context import LifespanContextT
16
+
17
+ from fastmcp.server import Context
18
+
11
19
  logger = get_logger(__name__)
12
20
 
13
21
 
@@ -69,14 +77,17 @@ class PromptManager:
69
77
  return prompt
70
78
 
71
79
  async def render_prompt(
72
- self, name: str, arguments: dict[str, Any] | None = None
80
+ self,
81
+ name: str,
82
+ arguments: dict[str, Any] | None = None,
83
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
73
84
  ) -> list[Message]:
74
85
  """Render a prompt by name with arguments."""
75
86
  prompt = self.get_prompt(name)
76
87
  if not prompt:
77
88
  raise NotFoundError(f"Unknown prompt: {name}")
78
89
 
79
- return await prompt.render(arguments)
90
+ return await prompt.render(arguments, context=context)
80
91
 
81
92
  def has_prompt(self, key: str) -> bool:
82
93
  """Check if a prompt exists."""
@@ -1,7 +1,9 @@
1
1
  """Base classes and interfaces for FastMCP resources."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import abc
4
- from typing import Annotated, Any
6
+ from typing import TYPE_CHECKING, Annotated, Any
5
7
 
6
8
  from mcp.types import Resource as MCPResource
7
9
  from pydantic import (
@@ -17,6 +19,12 @@ from pydantic import (
17
19
 
18
20
  from fastmcp.utilities.types import _convert_set_defaults
19
21
 
22
+ if TYPE_CHECKING:
23
+ from mcp.server.session import ServerSessionT
24
+ from mcp.shared.context import LifespanContextT
25
+
26
+ from fastmcp.server import Context
27
+
20
28
 
21
29
  class Resource(BaseModel, abc.ABC):
22
30
  """Base class for all resources."""
@@ -58,7 +66,9 @@ class Resource(BaseModel, abc.ABC):
58
66
  raise ValueError("Either name or uri must be provided")
59
67
 
60
68
  @abc.abstractmethod
61
- async def read(self) -> str | bytes:
69
+ async def read(
70
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
71
+ ) -> str | bytes:
62
72
  """Read the resource content."""
63
73
  pass
64
74
 
@@ -61,9 +61,16 @@ class ResourceManager:
61
61
  The added resource or template. If a resource or template with the same URI already exists,
62
62
  returns the existing resource or template.
63
63
  """
64
+ from fastmcp.server.context import Context
65
+
64
66
  # Check if this should be a template
65
67
  has_uri_params = "{" in uri and "}" in uri
66
- has_func_params = bool(inspect.signature(fn).parameters)
68
+ # check if the function has any parameters (other than injected context)
69
+ has_func_params = any(
70
+ p
71
+ for p in inspect.signature(fn).parameters.values()
72
+ if p.annotation is not Context
73
+ )
67
74
 
68
75
  if has_uri_params or has_func_params:
69
76
  return self.add_template_from_fn(
@@ -102,12 +109,12 @@ class ResourceManager:
102
109
  The added resource. If a resource with the same URI already exists,
103
110
  returns the existing resource.
104
111
  """
105
- resource = FunctionResource(
112
+ resource = FunctionResource.from_function(
113
+ fn=fn,
106
114
  uri=AnyUrl(uri),
107
115
  name=name,
108
116
  description=description,
109
117
  mime_type=mime_type or "text/plain",
110
- fn=fn,
111
118
  tags=tags or set(),
112
119
  )
113
120
  return self.add_resource(resource)
@@ -212,9 +219,13 @@ class ResourceManager:
212
219
  return True
213
220
  return False
214
221
 
215
- async def get_resource(self, uri: AnyUrl | str) -> Resource:
222
+ async def get_resource(self, uri: AnyUrl | str, context=None) -> Resource:
216
223
  """Get resource by URI, checking concrete resources first, then templates.
217
224
 
225
+ Args:
226
+ uri: The URI of the resource to get
227
+ context: Optional context object to pass to template resources
228
+
218
229
  Raises:
219
230
  NotFoundError: If no resource or template matching the URI is found.
220
231
  """
@@ -230,7 +241,11 @@ class ResourceManager:
230
241
  # Try to match against the storage key (which might be a custom key)
231
242
  if params := match_uri_template(uri_str, storage_key):
232
243
  try:
233
- return await template.create_resource(uri_str, params)
244
+ return await template.create_resource(
245
+ uri_str,
246
+ params=params,
247
+ context=context,
248
+ )
234
249
  except Exception as e:
235
250
  raise ValueError(f"Error creating resource from template: {e}")
236
251
 
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import inspect
6
6
  import re
7
7
  from collections.abc import Callable
8
- from typing import Annotated, Any
8
+ from typing import TYPE_CHECKING, Annotated, Any
9
9
  from urllib.parse import unquote
10
10
 
11
11
  from mcp.types import ResourceTemplate as MCPResourceTemplate
@@ -22,6 +22,12 @@ from pydantic import (
22
22
  from fastmcp.resources.types import FunctionResource, Resource
23
23
  from fastmcp.utilities.types import _convert_set_defaults
24
24
 
25
+ if TYPE_CHECKING:
26
+ from mcp.server.session import ServerSessionT
27
+ from mcp.shared.context import LifespanContextT
28
+
29
+ from fastmcp.server import Context
30
+
25
31
 
26
32
  def build_regex(template: str) -> re.Pattern:
27
33
  parts = re.split(r"(\{[^}]+\})", template)
@@ -70,6 +76,9 @@ class ResourceTemplate(BaseModel):
70
76
  parameters: dict[str, Any] = Field(
71
77
  description="JSON schema for function parameters"
72
78
  )
79
+ context_kwarg: str | None = Field(
80
+ None, description="Name of the kwarg that should receive context"
81
+ )
73
82
 
74
83
  @field_validator("mime_type", mode="before")
75
84
  @classmethod
@@ -88,18 +97,34 @@ class ResourceTemplate(BaseModel):
88
97
  description: str | None = None,
89
98
  mime_type: str | None = None,
90
99
  tags: set[str] | None = None,
100
+ context_kwarg: str | None = None,
91
101
  ) -> ResourceTemplate:
92
102
  """Create a template from a function."""
103
+ from fastmcp import Context
104
+
93
105
  func_name = name or fn.__name__
94
106
  if func_name == "<lambda>":
95
107
  raise ValueError("You must provide a name for lambda functions")
96
108
 
109
+ # Auto-detect context parameter if not provided
110
+ if context_kwarg is None:
111
+ if inspect.ismethod(fn) and hasattr(fn, "__func__"):
112
+ sig = inspect.signature(fn.__func__)
113
+ else:
114
+ sig = inspect.signature(fn)
115
+ for param_name, param in sig.parameters.items():
116
+ if param.annotation is Context:
117
+ context_kwarg = param_name
118
+ break
119
+
97
120
  # Validate that URI params match function params
98
121
  uri_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
99
122
  if not uri_params:
100
123
  raise ValueError("URI template must contain at least one parameter")
101
124
 
102
125
  func_params = set(inspect.signature(fn).parameters.keys())
126
+ if context_kwarg:
127
+ func_params.discard(context_kwarg)
103
128
 
104
129
  # get the parameters that are required
105
130
  required_params = {
@@ -107,6 +132,8 @@ class ResourceTemplate(BaseModel):
107
132
  for p in func_params
108
133
  if inspect.signature(fn).parameters[p].default is inspect.Parameter.empty
109
134
  }
135
+ if context_kwarg and context_kwarg in required_params:
136
+ required_params.discard(context_kwarg)
110
137
 
111
138
  if not required_params.issubset(uri_params):
112
139
  raise ValueError(
@@ -132,17 +159,28 @@ class ResourceTemplate(BaseModel):
132
159
  fn=fn,
133
160
  parameters=parameters,
134
161
  tags=tags or set(),
162
+ context_kwarg=context_kwarg,
135
163
  )
136
164
 
137
165
  def matches(self, uri: str) -> dict[str, Any] | None:
138
166
  """Check if URI matches template and extract parameters."""
139
167
  return match_uri_template(uri, self.uri_template)
140
168
 
141
- async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
169
+ async def create_resource(
170
+ self,
171
+ uri: str,
172
+ params: dict[str, Any],
173
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
174
+ ) -> Resource:
142
175
  """Create a resource from the template with the given parameters."""
143
176
  try:
177
+ # Add context to parameters if needed
178
+ kwargs = params.copy()
179
+ if self.context_kwarg is not None and context is not None:
180
+ kwargs[self.context_kwarg] = context
181
+
144
182
  # Call function and check if result is a coroutine
145
- result = self.fn(**params)
183
+ result = self.fn(**kwargs)
146
184
  if inspect.iscoroutine(result):
147
185
  result = await result
148
186
 
@@ -151,8 +189,9 @@ class ResourceTemplate(BaseModel):
151
189
  name=self.name,
152
190
  description=self.description,
153
191
  mime_type=self.mime_type,
154
- fn=lambda: result, # Capture result in closure
192
+ fn=lambda **kwargs: result, # Capture result in closure
155
193
  tags=self.tags,
194
+ context_kwarg=self.context_kwarg,
156
195
  )
157
196
  except Exception as e:
158
197
  raise ValueError(f"Error creating resource from template: {e}")
@@ -1,10 +1,12 @@
1
1
  """Concrete resource implementations."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import inspect
4
6
  import json
5
7
  from collections.abc import Callable
6
8
  from pathlib import Path
7
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
8
10
 
9
11
  import anyio
10
12
  import anyio.to_thread
@@ -13,15 +15,24 @@ import pydantic.json
13
15
  import pydantic_core
14
16
  from pydantic import Field, ValidationInfo
15
17
 
18
+ import fastmcp
16
19
  from fastmcp.resources.resource import Resource
17
20
 
21
+ if TYPE_CHECKING:
22
+ from mcp.server.session import ServerSessionT
23
+ from mcp.shared.context import LifespanContextT
24
+
25
+ from fastmcp.server import Context
26
+
18
27
 
19
28
  class TextResource(Resource):
20
29
  """A resource that reads from a string."""
21
30
 
22
31
  text: str = Field(description="Text content of the resource")
23
32
 
24
- async def read(self) -> str:
33
+ async def read(
34
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
35
+ ) -> str:
25
36
  """Read the text content."""
26
37
  return self.text
27
38
 
@@ -31,7 +42,9 @@ class BinaryResource(Resource):
31
42
 
32
43
  data: bytes = Field(description="Binary content of the resource")
33
44
 
34
- async def read(self) -> bytes:
45
+ async def read(
46
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
47
+ ) -> bytes:
35
48
  """Read the binary content."""
36
49
  return self.data
37
50
 
@@ -50,15 +63,40 @@ class FunctionResource(Resource):
50
63
  """
51
64
 
52
65
  fn: Callable[[], Any]
66
+ context_kwarg: str | None = Field(
67
+ default=None, description="Name of the kwarg that should receive context"
68
+ )
53
69
 
54
- async def read(self) -> str | bytes:
70
+ @classmethod
71
+ def from_function(
72
+ cls, fn: Callable[[], Any], context_kwarg: str | None = None, **kwargs
73
+ ) -> FunctionResource:
74
+ if context_kwarg is None:
75
+ parameters = inspect.signature(fn).parameters
76
+ context_param = next(
77
+ (p for p in parameters.values() if p.annotation is fastmcp.Context),
78
+ None,
79
+ )
80
+ if context_param is not None:
81
+ context_kwarg = context_param.name
82
+ return cls(fn=fn, context_kwarg=context_kwarg, **kwargs)
83
+
84
+ async def read(
85
+ self,
86
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
87
+ ) -> str | bytes:
55
88
  """Read the resource by calling the wrapped function."""
56
89
  try:
57
- result = (
58
- await self.fn() if inspect.iscoroutinefunction(self.fn) else self.fn()
59
- )
90
+ kwargs = {}
91
+ if self.context_kwarg is not None:
92
+ kwargs[self.context_kwarg] = context
93
+
94
+ result = self.fn(**kwargs)
95
+ if inspect.iscoroutinefunction(self.fn):
96
+ result = await result
97
+
60
98
  if isinstance(result, Resource):
61
- return await result.read()
99
+ return await result.read(context=context)
62
100
  if isinstance(result, bytes):
63
101
  return result
64
102
  if isinstance(result, str):
@@ -105,7 +143,9 @@ class FileResource(Resource):
105
143
  mime_type = info.data.get("mime_type", "text/plain")
106
144
  return not mime_type.startswith("text/")
107
145
 
108
- async def read(self) -> str | bytes:
146
+ async def read(
147
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
148
+ ) -> str | bytes:
109
149
  """Read the file content."""
110
150
  try:
111
151
  if self.is_binary:
@@ -123,7 +163,9 @@ class HttpResource(Resource):
123
163
  default="application/json", description="MIME type of the resource content"
124
164
  )
125
165
 
126
- async def read(self) -> str | bytes:
166
+ async def read(
167
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
168
+ ) -> str | bytes:
127
169
  """Read the HTTP content."""
128
170
  async with httpx.AsyncClient() as client:
129
171
  response = await client.get(self.url)
@@ -175,7 +217,9 @@ class DirectoryResource(Resource):
175
217
  except Exception as e:
176
218
  raise ValueError(f"Error listing directory {self.path}: {e}")
177
219
 
178
- async def read(self) -> str: # Always returns JSON string
220
+ async def read(
221
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
222
+ ) -> str: # Always returns JSON string
179
223
  """Read the directory listing."""
180
224
  try:
181
225
  files = await anyio.to_thread.run_sync(self.list_files)