From e196384a6a116caa947ff86191fa03e559dfe342 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:59:57 +0000 Subject: [PATCH 1/4] Change middleware return types from list to Sequence This fixes covariance issues in middleware method signatures by using Sequence instead of list for return type annotations. Lists are invariant while Sequences are covariant, allowing middleware subclasses to properly type their return values. Fixes #2055 Co-authored-by: William Easton --- src/fastmcp/server/middleware/middleware.py | 26 ++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/fastmcp/server/middleware/middleware.py b/src/fastmcp/server/middleware/middleware.py index 0b78e4866..ba2d25c23 100644 --- a/src/fastmcp/server/middleware/middleware.py +++ b/src/fastmcp/server/middleware/middleware.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from collections.abc import Awaitable +from collections.abc import Awaitable, Sequence from dataclasses import dataclass, field, replace from datetime import datetime, timezone from functools import partial @@ -164,8 +164,10 @@ class Middleware: async def on_read_resource( self, context: MiddlewareContext[mt.ReadResourceRequestParams], - call_next: CallNext[mt.ReadResourceRequestParams, list[ReadResourceContents]], - ) -> list[ReadResourceContents]: + call_next: CallNext[ + mt.ReadResourceRequestParams, Sequence[ReadResourceContents] + ], + ) -> Sequence[ReadResourceContents]: return await call_next(context) async def on_get_prompt( @@ -178,27 +180,29 @@ class Middleware: async def on_list_tools( self, context: MiddlewareContext[mt.ListToolsRequest], - call_next: CallNext[mt.ListToolsRequest, list[Tool]], - ) -> list[Tool]: + call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]], + ) -> Sequence[Tool]: return await call_next(context) async def on_list_resources( self, context: MiddlewareContext[mt.ListResourcesRequest], - call_next: CallNext[mt.ListResourcesRequest, list[Resource]], - ) -> list[Resource]: + call_next: CallNext[mt.ListResourcesRequest, Sequence[Resource]], + ) -> Sequence[Resource]: return await call_next(context) async def on_list_resource_templates( self, context: MiddlewareContext[mt.ListResourceTemplatesRequest], - call_next: CallNext[mt.ListResourceTemplatesRequest, list[ResourceTemplate]], - ) -> list[ResourceTemplate]: + call_next: CallNext[ + mt.ListResourceTemplatesRequest, Sequence[ResourceTemplate] + ], + ) -> Sequence[ResourceTemplate]: return await call_next(context) async def on_list_prompts( self, context: MiddlewareContext[mt.ListPromptsRequest], - call_next: CallNext[mt.ListPromptsRequest, list[Prompt]], - ) -> list[Prompt]: + call_next: CallNext[mt.ListPromptsRequest, Sequence[Prompt]], + ) -> Sequence[Prompt]: return await call_next(context) From 6bd3f85cb26e7a2705d18dde947be474f032705f Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 12 Oct 2025 09:34:25 -0400 Subject: [PATCH 2/4] Add't typing changes for middleware --- src/fastmcp/server/middleware/middleware.py | 8 ++-- src/fastmcp/server/server.py | 46 ++++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/fastmcp/server/middleware/middleware.py b/src/fastmcp/server/middleware/middleware.py index ba2d25c23..38b99b316 100644 --- a/src/fastmcp/server/middleware/middleware.py +++ b/src/fastmcp/server/middleware/middleware.py @@ -135,15 +135,15 @@ class Middleware: async def on_request( self, - context: MiddlewareContext[mt.Request], - call_next: CallNext[mt.Request, Any], + context: MiddlewareContext[mt.Request[Any, Any]], + call_next: CallNext[mt.Request[Any, Any], Any], ) -> Any: return await call_next(context) async def on_notification( self, - context: MiddlewareContext[mt.Notification], - call_next: CallNext[mt.Notification, Any], + context: MiddlewareContext[mt.Notification[Any, Any]], + call_next: CallNext[mt.Notification[Any, Any], Any], ) -> Any: return await call_next(context) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 81290e5cd..64227babd 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -641,7 +641,11 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return await self._apply_middleware(mw_context, self._list_tools) + return list[Tool]( + await self._apply_middleware( + context=mw_context, call_next=self._list_tools + ) + ) async def _list_tools( self, @@ -721,7 +725,11 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return await self._apply_middleware(mw_context, self._list_resources) + return list[Resource]( + await self._apply_middleware( + context=mw_context, call_next=self._list_resources + ) + ) async def _list_resources( self, @@ -811,8 +819,10 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return await self._apply_middleware( - mw_context, self._list_resource_templates + return list[ResourceTemplate]( + await self._apply_middleware( + context=mw_context, call_next=self._list_resource_templates + ) ) async def _list_resource_templates( @@ -907,7 +917,11 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return await self._apply_middleware(mw_context, self._list_prompts) + return list[Prompt]( + await self._apply_middleware( + context=mw_context, call_next=self._list_prompts + ) + ) async def _list_prompts( self, @@ -1002,7 +1016,9 @@ class FastMCP(Generic[LifespanResultT]): method="tools/call", fastmcp_context=fastmcp.server.dependencies.get_context(), ) - return await self._apply_middleware(mw_context, self._call_tool) + return await self._apply_middleware( + context=mw_context, call_next=self._call_tool + ) async def _call_tool( self, @@ -1056,7 +1072,9 @@ class FastMCP(Generic[LifespanResultT]): async with fastmcp.server.context.Context(fastmcp=self): try: - return await self._read_resource_middleware(uri) + return list[ReadResourceContents]( + await self._read_resource_middleware(uri) + ) except DisabledError: # convert to NotFoundError to avoid leaking resource presence raise NotFoundError(f"Unknown resource: {str(uri)!r}") @@ -1085,7 +1103,11 @@ class FastMCP(Generic[LifespanResultT]): method="resources/read", fastmcp_context=fastmcp.server.dependencies.get_context(), ) - return await self._apply_middleware(mw_context, self._read_resource) + return list[ReadResourceContents]( + await self._apply_middleware( + context=mw_context, call_next=self._read_resource + ) + ) async def _read_resource( self, @@ -1114,7 +1136,9 @@ class FastMCP(Generic[LifespanResultT]): if not self._should_enable_component(resource): # Parent filter blocks this resource, continue searching continue - result = await mounted.server._read_resource_middleware(key) + result = list[ReadResourceContents]( + await mounted.server._read_resource_middleware(key) + ) return result except NotFoundError: continue @@ -1173,7 +1197,9 @@ class FastMCP(Generic[LifespanResultT]): method="prompts/get", fastmcp_context=fastmcp.server.dependencies.get_context(), ) - return await self._apply_middleware(mw_context, self._get_prompt) + return await self._apply_middleware( + context=mw_context, call_next=self._get_prompt + ) async def _get_prompt( self, From 16ebb666d6bc320849c326a88ff4f172c921ef8c Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 12 Oct 2025 21:34:29 -0400 Subject: [PATCH 3/4] stop subscripting list init --- src/fastmcp/server/server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 64227babd..97f14a7c9 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -641,7 +641,7 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return list[Tool]( + return list( await self._apply_middleware( context=mw_context, call_next=self._list_tools ) @@ -725,7 +725,7 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return list[Resource]( + return list( await self._apply_middleware( context=mw_context, call_next=self._list_resources ) @@ -819,7 +819,7 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return list[ResourceTemplate]( + return list( await self._apply_middleware( context=mw_context, call_next=self._list_resource_templates ) @@ -917,7 +917,7 @@ class FastMCP(Generic[LifespanResultT]): ) # Apply the middleware chain. - return list[Prompt]( + return list( await self._apply_middleware( context=mw_context, call_next=self._list_prompts ) @@ -1103,7 +1103,7 @@ class FastMCP(Generic[LifespanResultT]): method="resources/read", fastmcp_context=fastmcp.server.dependencies.get_context(), ) - return list[ReadResourceContents]( + return list( await self._apply_middleware( context=mw_context, call_next=self._read_resource ) From 7e873168172fd59c34d8aedda0efe90a0f98aa30 Mon Sep 17 00:00:00 2001 From: William Easton Date: Mon, 13 Oct 2025 10:23:23 -0500 Subject: [PATCH 4/4] Missed one --- src/fastmcp/server/server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 97f14a7c9..25db16cd1 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1136,9 +1136,7 @@ class FastMCP(Generic[LifespanResultT]): if not self._should_enable_component(resource): # Parent filter blocks this resource, continue searching continue - result = list[ReadResourceContents]( - await mounted.server._read_resource_middleware(key) - ) + result = list(await mounted.server._read_resource_middleware(key)) return result except NotFoundError: continue