fastmcp 2.13.2__py3-none-any.whl → 2.14.0__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.
Files changed (74) hide show
  1. fastmcp/__init__.py +0 -21
  2. fastmcp/cli/__init__.py +0 -3
  3. fastmcp/cli/__main__.py +5 -0
  4. fastmcp/cli/cli.py +8 -22
  5. fastmcp/cli/install/shared.py +0 -15
  6. fastmcp/cli/tasks.py +110 -0
  7. fastmcp/client/auth/oauth.py +9 -9
  8. fastmcp/client/client.py +665 -129
  9. fastmcp/client/elicitation.py +11 -5
  10. fastmcp/client/messages.py +7 -5
  11. fastmcp/client/roots.py +2 -1
  12. fastmcp/client/tasks.py +614 -0
  13. fastmcp/client/transports.py +37 -5
  14. fastmcp/contrib/component_manager/component_service.py +4 -20
  15. fastmcp/dependencies.py +25 -0
  16. fastmcp/experimental/sampling/handlers/openai.py +1 -1
  17. fastmcp/experimental/server/openapi/__init__.py +15 -13
  18. fastmcp/experimental/utilities/openapi/__init__.py +12 -38
  19. fastmcp/prompts/prompt.py +33 -33
  20. fastmcp/resources/resource.py +29 -12
  21. fastmcp/resources/template.py +64 -54
  22. fastmcp/server/auth/__init__.py +0 -9
  23. fastmcp/server/auth/auth.py +127 -3
  24. fastmcp/server/auth/oauth_proxy.py +47 -97
  25. fastmcp/server/auth/oidc_proxy.py +7 -0
  26. fastmcp/server/auth/providers/in_memory.py +2 -2
  27. fastmcp/server/auth/providers/oci.py +2 -2
  28. fastmcp/server/context.py +66 -72
  29. fastmcp/server/dependencies.py +464 -6
  30. fastmcp/server/elicitation.py +285 -47
  31. fastmcp/server/event_store.py +177 -0
  32. fastmcp/server/http.py +15 -3
  33. fastmcp/server/low_level.py +56 -12
  34. fastmcp/server/middleware/middleware.py +2 -2
  35. fastmcp/server/openapi/__init__.py +35 -0
  36. fastmcp/{experimental/server → server}/openapi/components.py +4 -3
  37. fastmcp/{experimental/server → server}/openapi/routing.py +1 -1
  38. fastmcp/{experimental/server → server}/openapi/server.py +6 -5
  39. fastmcp/server/proxy.py +50 -37
  40. fastmcp/server/server.py +731 -532
  41. fastmcp/server/tasks/__init__.py +21 -0
  42. fastmcp/server/tasks/capabilities.py +22 -0
  43. fastmcp/server/tasks/config.py +89 -0
  44. fastmcp/server/tasks/converters.py +205 -0
  45. fastmcp/server/tasks/handlers.py +356 -0
  46. fastmcp/server/tasks/keys.py +93 -0
  47. fastmcp/server/tasks/protocol.py +355 -0
  48. fastmcp/server/tasks/subscriptions.py +205 -0
  49. fastmcp/settings.py +101 -103
  50. fastmcp/tools/tool.py +80 -44
  51. fastmcp/tools/tool_transform.py +1 -12
  52. fastmcp/utilities/components.py +3 -3
  53. fastmcp/utilities/json_schema_type.py +4 -4
  54. fastmcp/utilities/mcp_config.py +1 -2
  55. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +1 -1
  56. fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
  57. fastmcp/utilities/openapi/__init__.py +63 -0
  58. fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
  59. fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +1 -1
  60. fastmcp/utilities/tests.py +11 -5
  61. fastmcp/utilities/types.py +8 -0
  62. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/METADATA +5 -4
  63. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/RECORD +71 -59
  64. fastmcp/server/auth/providers/bearer.py +0 -25
  65. fastmcp/server/openapi.py +0 -1087
  66. fastmcp/utilities/openapi.py +0 -1568
  67. /fastmcp/{experimental/server → server}/openapi/README.md +0 -0
  68. /fastmcp/{experimental/utilities → utilities}/openapi/director.py +0 -0
  69. /fastmcp/{experimental/utilities → utilities}/openapi/models.py +0 -0
  70. /fastmcp/{experimental/utilities → utilities}/openapi/parser.py +0 -0
  71. /fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +0 -0
  72. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +0 -0
  73. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
  74. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
7
7
  import anyio
8
8
  import mcp.types
9
9
  from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
10
+ from mcp import McpError
10
11
  from mcp.server.lowlevel.server import (
11
12
  LifespanResultT,
12
13
  NotificationOptions,
@@ -35,6 +36,8 @@ class MiddlewareServerSession(ServerSession):
35
36
  def __init__(self, fastmcp: FastMCP, *args, **kwargs):
36
37
  super().__init__(*args, **kwargs)
37
38
  self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)
39
+ # Task group for subscription tasks (set during session run)
40
+ self._subscription_task_group: anyio.TaskGroup | None = None # type: ignore[valid-type]
38
41
 
39
42
  @property
40
43
  def fastmcp(self) -> FastMCP:
@@ -49,23 +52,46 @@ class MiddlewareServerSession(ServerSession):
49
52
  responder: RequestResponder[mcp.types.ClientRequest, mcp.types.ServerResult],
50
53
  ):
51
54
  """
52
- Override the _received_request method to route initialization requests
55
+ Override the _received_request method to route special requests
53
56
  through FastMCP middleware.
54
57
 
55
- These are not handled by routes that FastMCP typically overrides and
56
- require special handling.
58
+ Handles initialization requests and SEP-1686 task methods.
57
59
  """
58
60
  import fastmcp.server.context
59
61
  from fastmcp.server.middleware.middleware import MiddlewareContext
60
62
 
61
63
  if isinstance(responder.request.root, mcp.types.InitializeRequest):
64
+ # The MCP SDK's ServerSession._received_request() handles the
65
+ # initialize request internally by calling responder.respond()
66
+ # to send the InitializeResult directly to the write stream, then
67
+ # returning None. This bypasses the middleware return path entirely,
68
+ # so middleware would only see the request, never the response.
69
+ #
70
+ # To expose the response to middleware (e.g., for logging server
71
+ # capabilities), we wrap responder.respond() to capture the
72
+ # InitializeResult before it's sent, then return it from
73
+ # call_original_handler so it flows back through the middleware chain.
74
+ captured_response: mcp.types.ServerResult | None = None
75
+ original_respond = responder.respond
76
+
77
+ async def capturing_respond(
78
+ response: mcp.types.ServerResult,
79
+ ) -> None:
80
+ nonlocal captured_response
81
+ captured_response = response
82
+ return await original_respond(response)
83
+
84
+ responder.respond = capturing_respond # type: ignore[method-assign]
62
85
 
63
86
  async def call_original_handler(
64
87
  ctx: MiddlewareContext,
65
- ) -> None:
66
- return await super(MiddlewareServerSession, self)._received_request(
67
- responder
68
- )
88
+ ) -> mcp.types.InitializeResult | None:
89
+ await super(MiddlewareServerSession, self)._received_request(responder)
90
+ if captured_response is not None and isinstance(
91
+ captured_response.root, mcp.types.InitializeResult
92
+ ):
93
+ return captured_response.root
94
+ return None
69
95
 
70
96
  async with fastmcp.server.context.Context(
71
97
  fastmcp=self.fastmcp
@@ -79,11 +105,26 @@ class MiddlewareServerSession(ServerSession):
79
105
  fastmcp_context=fastmcp_ctx,
80
106
  )
81
107
 
82
- return await self.fastmcp._apply_middleware(
83
- mw_context, call_original_handler
84
- )
85
- else:
86
- return await super()._received_request(responder)
108
+ try:
109
+ return await self.fastmcp._apply_middleware(
110
+ mw_context, call_original_handler
111
+ )
112
+ except McpError as e:
113
+ # McpError can be thrown from middleware in `on_initialize`
114
+ # send the error to responder.
115
+ if not responder._completed:
116
+ with responder:
117
+ await responder.respond(e.error)
118
+ else:
119
+ # Don't re-raise: prevents responding to initialize request twice
120
+ logger.warning(
121
+ "Received McpError but responder is already completed. "
122
+ "Cannot send error response as response was already sent.",
123
+ exc_info=e,
124
+ )
125
+
126
+ # Fall through to default handling (task methods now handled via registered handlers)
127
+ return await super()._received_request(responder)
87
128
 
88
129
 
89
130
  class LowLevelServer(_Server[LifespanResultT, RequestT]):
@@ -146,6 +187,9 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
146
187
  )
147
188
 
148
189
  async with anyio.create_task_group() as tg:
190
+ # Store task group on session for subscription tasks (SEP-1686)
191
+ session._subscription_task_group = tg
192
+
149
193
  async for message in session.incoming_messages:
150
194
  tg.start_soon(
151
195
  self._handle_message,
@@ -150,8 +150,8 @@ class Middleware:
150
150
  async def on_initialize(
151
151
  self,
152
152
  context: MiddlewareContext[mt.InitializeRequest],
153
- call_next: CallNext[mt.InitializeRequest, None],
154
- ) -> None:
153
+ call_next: CallNext[mt.InitializeRequest, mt.InitializeResult | None],
154
+ ) -> mt.InitializeResult | None:
155
155
  return await call_next(context)
156
156
 
157
157
  async def on_call_tool(
@@ -0,0 +1,35 @@
1
+ """OpenAPI server implementation for FastMCP - refactored for better maintainability."""
2
+
3
+ # Import from server
4
+ from .server import FastMCPOpenAPI
5
+
6
+ # Import from routing
7
+ from .routing import (
8
+ MCPType,
9
+ RouteMap,
10
+ RouteMapFn,
11
+ ComponentFn,
12
+ DEFAULT_ROUTE_MAPPINGS,
13
+ _determine_route_type,
14
+ )
15
+
16
+ # Import from components
17
+ from .components import (
18
+ OpenAPITool,
19
+ OpenAPIResource,
20
+ OpenAPIResourceTemplate,
21
+ )
22
+
23
+ # Export public symbols - maintaining backward compatibility
24
+ __all__ = [
25
+ "DEFAULT_ROUTE_MAPPINGS",
26
+ "ComponentFn",
27
+ "FastMCPOpenAPI",
28
+ "MCPType",
29
+ "OpenAPIResource",
30
+ "OpenAPIResourceTemplate",
31
+ "OpenAPITool",
32
+ "RouteMap",
33
+ "RouteMapFn",
34
+ "_determine_route_type",
35
+ ]
@@ -9,14 +9,15 @@ import httpx
9
9
  from mcp.types import ToolAnnotations
10
10
  from pydantic.networks import AnyUrl
11
11
 
12
- # Import from our new utilities
13
- from fastmcp.experimental.utilities.openapi import HTTPRoute
14
- from fastmcp.experimental.utilities.openapi.director import RequestDirector
15
12
  from fastmcp.resources import Resource, ResourceTemplate
16
13
  from fastmcp.server.dependencies import get_http_headers
17
14
  from fastmcp.tools.tool import Tool, ToolResult
18
15
  from fastmcp.utilities.logging import get_logger
19
16
 
17
+ # Import from our new utilities
18
+ from fastmcp.utilities.openapi import HTTPRoute
19
+ from fastmcp.utilities.openapi.director import RequestDirector
20
+
20
21
  if TYPE_CHECKING:
21
22
  from fastmcp.server import Context
22
23
 
@@ -14,8 +14,8 @@ if TYPE_CHECKING:
14
14
  OpenAPITool,
15
15
  )
16
16
  # Import from our new utilities
17
- from fastmcp.experimental.utilities.openapi import HttpMethod, HTTPRoute
18
17
  from fastmcp.utilities.logging import get_logger
18
+ from fastmcp.utilities.openapi import HttpMethod, HTTPRoute
19
19
 
20
20
  logger = get_logger(__name__)
21
21
 
@@ -7,16 +7,17 @@ from typing import Any, Literal
7
7
  import httpx
8
8
  from jsonschema_path import SchemaPath
9
9
 
10
+ from fastmcp.server.server import FastMCP
11
+ from fastmcp.utilities.logging import get_logger
12
+
10
13
  # Import from our new utilities and components
11
- from fastmcp.experimental.utilities.openapi import (
14
+ from fastmcp.utilities.openapi import (
12
15
  HTTPRoute,
13
16
  extract_output_schema_from_responses,
14
17
  format_simple_description,
15
18
  parse_openapi_to_http_routes,
16
19
  )
17
- from fastmcp.experimental.utilities.openapi.director import RequestDirector
18
- from fastmcp.server.server import FastMCP
19
- from fastmcp.utilities.logging import get_logger
20
+ from fastmcp.utilities.openapi.director import RequestDirector
20
21
 
21
22
  from .components import (
22
23
  OpenAPIResource,
@@ -247,7 +248,7 @@ class FastMCPOpenAPI(FastMCP):
247
248
  # Create the new name
248
249
  new_name = f"{name}_{self._used_names[component_type][name]}"
249
250
  logger.debug(
250
- f"Name collision detected: '{name}' already exists as a {component_type[:-1]}. "
251
+ f"Name collision detected: '{name}' already exists as a {component_type}. "
251
252
  f"Using '{new_name}' instead."
252
253
  )
253
254
 
fastmcp/server/proxy.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
- import warnings
5
4
  from collections.abc import Awaitable, Callable
6
5
  from pathlib import Path
7
6
  from typing import TYPE_CHECKING, Any, cast
@@ -15,12 +14,12 @@ from mcp.shared.exceptions import McpError
15
14
  from mcp.types import (
16
15
  METHOD_NOT_FOUND,
17
16
  BlobResourceContents,
17
+ ElicitRequestFormParams,
18
18
  GetPromptResult,
19
19
  TextResourceContents,
20
20
  )
21
21
  from pydantic.networks import AnyUrl
22
22
 
23
- import fastmcp
24
23
  from fastmcp.client.client import Client, FastMCP1Server
25
24
  from fastmcp.client.elicitation import ElicitResult
26
25
  from fastmcp.client.logging import LogMessage
@@ -36,6 +35,7 @@ from fastmcp.resources.resource_manager import ResourceManager
36
35
  from fastmcp.server.context import Context
37
36
  from fastmcp.server.dependencies import get_context
38
37
  from fastmcp.server.server import FastMCP
38
+ from fastmcp.server.tasks.config import TaskConfig
39
39
  from fastmcp.tools.tool import Tool, ToolResult
40
40
  from fastmcp.tools.tool_manager import ToolManager
41
41
  from fastmcp.tools.tool_transform import (
@@ -117,6 +117,7 @@ class ProxyToolManager(ToolManager, ProxyManagerMixin):
117
117
  return ToolResult(
118
118
  content=result.content,
119
119
  structured_content=result.structured_content,
120
+ meta=result.meta,
120
121
  )
121
122
 
122
123
 
@@ -260,6 +261,8 @@ class ProxyTool(Tool, MirroredComponent):
260
261
  A Tool that represents and executes a tool on a remote server.
261
262
  """
262
263
 
264
+ task_config: TaskConfig = TaskConfig(mode="forbidden")
265
+
263
266
  def __init__(self, client: Client, **kwargs: Any):
264
267
  super().__init__(**kwargs)
265
268
  self._client = client
@@ -288,15 +291,37 @@ class ProxyTool(Tool, MirroredComponent):
288
291
  ) -> ToolResult:
289
292
  """Executes the tool by making a call through the client."""
290
293
  async with self._client:
294
+ context = get_context()
295
+ # Build meta dict from request context
296
+ meta: dict[str, Any] | None = None
297
+ if hasattr(context, "request_context"):
298
+ req_ctx = context.request_context
299
+ # Start with existing meta if present
300
+ if hasattr(req_ctx, "meta") and req_ctx.meta:
301
+ meta = dict(req_ctx.meta)
302
+ # Add task metadata if this is a task request
303
+ if (
304
+ hasattr(req_ctx, "experimental")
305
+ and hasattr(req_ctx.experimental, "is_task")
306
+ and req_ctx.experimental.is_task
307
+ ):
308
+ task_metadata = req_ctx.experimental.task_metadata
309
+ if task_metadata:
310
+ meta = meta or {}
311
+ meta["modelcontextprotocol.io/task"] = task_metadata.model_dump(
312
+ exclude_none=True
313
+ )
314
+
291
315
  result = await self._client.call_tool_mcp(
292
- name=self.name,
293
- arguments=arguments,
316
+ name=self.name, arguments=arguments, meta=meta
294
317
  )
295
318
  if result.isError:
296
319
  raise ToolError(cast(mcp.types.TextContent, result.content[0]).text)
320
+ # Preserve backend's meta (includes task metadata for background tasks)
297
321
  return ToolResult(
298
322
  content=result.content,
299
323
  structured_content=result.structuredContent,
324
+ meta=result.meta,
300
325
  )
301
326
 
302
327
 
@@ -305,6 +330,7 @@ class ProxyResource(Resource, MirroredComponent):
305
330
  A Resource that represents and reads a resource from a remote server.
306
331
  """
307
332
 
333
+ task_config: TaskConfig = TaskConfig(mode="forbidden")
308
334
  _client: Client
309
335
  _value: str | bytes | None = None
310
336
 
@@ -337,6 +363,7 @@ class ProxyResource(Resource, MirroredComponent):
337
363
  icons=mcp_resource.icons,
338
364
  meta=mcp_resource.meta,
339
365
  tags=(mcp_resource.meta or {}).get("_fastmcp", {}).get("tags", []),
366
+ task_config=TaskConfig(mode="forbidden"),
340
367
  _mirrored=True,
341
368
  )
342
369
 
@@ -360,12 +387,14 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
360
387
  A ResourceTemplate that represents and creates resources from a remote server template.
361
388
  """
362
389
 
390
+ task_config: TaskConfig = TaskConfig(mode="forbidden")
391
+
363
392
  def __init__(self, client: Client, **kwargs: Any):
364
393
  super().__init__(**kwargs)
365
394
  self._client = client
366
395
 
367
396
  @classmethod
368
- def from_mcp_template(
397
+ def from_mcp_template( # type: ignore[override]
369
398
  cls, client: Client, mcp_template: mcp.types.ResourceTemplate
370
399
  ) -> ProxyTemplate:
371
400
  """Factory method to create a ProxyTemplate from a raw MCP template schema."""
@@ -380,6 +409,7 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
380
409
  parameters={}, # Remote templates don't have local parameters
381
410
  meta=mcp_template.meta,
382
411
  tags=(mcp_template.meta or {}).get("_fastmcp", {}).get("tags", []),
412
+ task_config=TaskConfig(mode="forbidden"),
383
413
  _mirrored=True,
384
414
  )
385
415
 
@@ -425,6 +455,7 @@ class ProxyPrompt(Prompt, MirroredComponent):
425
455
  A Prompt that represents and renders a prompt from a remote server.
426
456
  """
427
457
 
458
+ task_config: TaskConfig = TaskConfig(mode="forbidden")
428
459
  _client: Client
429
460
 
430
461
  def __init__(self, client: Client, **kwargs):
@@ -453,10 +484,11 @@ class ProxyPrompt(Prompt, MirroredComponent):
453
484
  icons=mcp_prompt.icons,
454
485
  meta=mcp_prompt.meta,
455
486
  tags=(mcp_prompt.meta or {}).get("_fastmcp", {}).get("tags", []),
487
+ task_config=TaskConfig(mode="forbidden"),
456
488
  _mirrored=True,
457
489
  )
458
490
 
459
- async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
491
+ async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]: # type: ignore[override]
460
492
  """Render the prompt by making a call through the client."""
461
493
  async with self._client:
462
494
  result = await self._client.get_prompt(self.name, arguments)
@@ -471,9 +503,8 @@ class FastMCPProxy(FastMCP):
471
503
 
472
504
  def __init__(
473
505
  self,
474
- client: Client | None = None,
475
506
  *,
476
- client_factory: ClientFactoryT | None = None,
507
+ client_factory: ClientFactoryT,
477
508
  **kwargs,
478
509
  ):
479
510
  """
@@ -483,9 +514,6 @@ class FastMCPProxy(FastMCP):
483
514
  Use FastMCP.as_proxy() for convenience with automatic session strategy.
484
515
 
485
516
  Args:
486
- client: [DEPRECATED] A Client instance. Use client_factory instead for explicit
487
- session management. When provided, a client_factory will be automatically
488
- created that provides session isolation for backwards compatibility.
489
517
  client_factory: A callable that returns a Client instance when called.
490
518
  This gives you full control over session creation and reuse.
491
519
  Can be either a synchronous or asynchronous function.
@@ -494,29 +522,7 @@ class FastMCPProxy(FastMCP):
494
522
 
495
523
  super().__init__(**kwargs)
496
524
 
497
- # Handle client and client_factory parameters
498
- if client is not None and client_factory is not None:
499
- raise ValueError("Cannot specify both 'client' and 'client_factory'")
500
-
501
- if client is not None:
502
- # Deprecated in 2.10.3
503
- if fastmcp.settings.deprecation_warnings:
504
- warnings.warn(
505
- "Passing 'client' to FastMCPProxy is deprecated. Use 'client_factory' instead for explicit session management. "
506
- "For automatic session strategy, use FastMCP.as_proxy().",
507
- DeprecationWarning,
508
- stacklevel=2,
509
- )
510
-
511
- # Create a factory that provides session isolation for backwards compatibility
512
- def deprecated_client_factory():
513
- return client.new()
514
-
515
- self.client_factory = deprecated_client_factory
516
- elif client_factory is not None:
517
- self.client_factory = client_factory
518
- else:
519
- raise ValueError("Must specify 'client_factory'")
525
+ self.client_factory = client_factory
520
526
 
521
527
  # Replace the default managers with our specialized proxy managers.
522
528
  self._tool_manager = ProxyToolManager(
@@ -595,7 +601,8 @@ class ProxyClient(Client[ClientTransportT]):
595
601
  return mcp.types.CreateMessageResult(
596
602
  role="assistant",
597
603
  model="fastmcp-client",
598
- content=content,
604
+ # TODO(ty): remove when ty supports isinstance exclusion narrowing
605
+ content=content, # type: ignore[arg-type]
599
606
  )
600
607
 
601
608
  @classmethod
@@ -610,9 +617,15 @@ class ProxyClient(Client[ClientTransportT]):
610
617
  A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
611
618
  """
612
619
  ctx = get_context()
620
+ # requestedSchema only exists on ElicitRequestFormParams, not ElicitRequestURLParams
621
+ requested_schema = (
622
+ params.requestedSchema
623
+ if isinstance(params, ElicitRequestFormParams)
624
+ else {"type": "object", "properties": {}}
625
+ )
613
626
  result = await ctx.session.elicit(
614
627
  message=message,
615
- requestedSchema=params.requestedSchema,
628
+ requestedSchema=requested_schema,
616
629
  related_request_id=ctx.request_id,
617
630
  )
618
631
  return ElicitResult(action=result.action, content=result.content)
@@ -656,7 +669,7 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
656
669
  super().__init__(*args, **kwargs)
657
670
  self._caches: dict[ServerSession, Client[ClientTransportT]] = {}
658
671
 
659
- async def __aexit__(self, exc_type, exc_value, traceback) -> None:
672
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None: # type: ignore[override]
660
673
  """
661
674
  The stateful proxy client will be forced disconnected when the session is exited.
662
675
  So we do nothing here.