Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/fastmcp/server/low_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async def call_original_handler(
)

try:
return await self.fastmcp._apply_middleware(
return await self.fastmcp._run_middleware(
mw_context, call_original_handler
)
except McpError as e:
Expand Down
99 changes: 22 additions & 77 deletions src/fastmcp/server/providers/fastmcp_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,19 @@ async def _run(
async def run(
self, arguments: dict[str, Any]
) -> ToolResult | mcp.types.CreateTaskResult: # type: ignore[override]
"""Delegate to child server's middleware chain.
"""Delegate to child server's call_tool().

This runs BEFORE any backgrounding decision - the actual underlying
tool will check contextvars and submit to Docket if appropriate.
"""
return await self._server._call_tool_middleware(self._original_name, arguments)
return await self._server.call_tool(self._original_name, arguments)


class FastMCPProviderResource(Resource):
"""Resource that delegates reading to a wrapped server's middleware.
"""Resource that delegates reading to a wrapped server's read_resource().

When `read()` is called, this resource invokes the wrapped server's
`_read_resource_middleware()` method, ensuring the server's middleware
chain is executed.
`read_resource()` method, ensuring the server's middleware chain is executed.
"""

_server: Any = None # FastMCP, but Any to avoid circular import
Expand Down Expand Up @@ -150,44 +149,25 @@ async def _read(self) -> ResourceContent | mcp.types.CreateTaskResult:
return await self.read()

async def read(self) -> ResourceContent | mcp.types.CreateTaskResult: # type: ignore[override]
"""Delegate to child server's middleware.

When called from a Docket worker (background task), there's no FastMCP
context set up, so we create one for the child server.
"""Delegate to child server's read_resource().

Note: The _docket_fn_key contextvar is intentionally NOT updated here.
The parent set it to the full namespaced key (e.g., data://c/gc/value)
which is what the function is registered under in Docket. All provider
layers pass this through unchanged so the eventual resource._read()
uses the correct Docket lookup key.
"""
import fastmcp.server.context

try:
from fastmcp.server.dependencies import get_context

get_context() # Will raise if no context
result = await self._server._read_resource_middleware(self._original_uri)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]
except RuntimeError:
# No context (e.g., Docket worker) - create one for the child server
async with fastmcp.server.context.Context(fastmcp=self._server):
result = await self._server._read_resource_middleware(
self._original_uri
)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]
result = await self._server.read_resource(self._original_uri)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]


class FastMCPProviderPrompt(Prompt):
"""Prompt that delegates rendering to a wrapped server's middleware.
"""Prompt that delegates rendering to a wrapped server's render_prompt().

When `render()` is called, this prompt invokes the wrapped server's
`_get_prompt_content_middleware()` method, ensuring the server's middleware
chain is executed.
`render_prompt()` method, ensuring the server's middleware chain is executed.
"""

_server: Any = None # FastMCP, but Any to avoid circular import
Expand Down Expand Up @@ -229,38 +209,15 @@ async def _render(
async def render(
self, arguments: dict[str, Any] | None = None
) -> PromptResult | mcp.types.CreateTaskResult: # type: ignore[override]
"""Delegate to child server's middleware.

When called from a Docket worker (background task), there's no FastMCP
context set up, so we create one for the child server.
"""Delegate to child server's render_prompt().

Note: The _docket_fn_key contextvar is intentionally NOT updated here.
The parent set it to the full namespaced name (e.g., c_gc_greet) which
is what the function is registered under in Docket. All provider layers
pass this through unchanged so the eventual prompt._render() uses the
correct Docket lookup key.
"""
import fastmcp.server.context

try:
from fastmcp.server.dependencies import get_context

get_context() # Will raise if no context
result = await self._server._get_prompt_content_middleware(
self._original_name, arguments
)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result
except RuntimeError:
# No context (e.g., Docket worker) - create one for the child server
async with fastmcp.server.context.Context(fastmcp=self._server):
result = await self._server._get_prompt_content_middleware(
self._original_name, arguments
)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result
return await self._server.render_prompt(self._original_name, arguments)


class FastMCPProviderResourceTemplate(ResourceTemplate):
Expand Down Expand Up @@ -323,7 +280,7 @@ async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
async def _read(
self, uri: str, params: dict[str, Any]
) -> ResourceContent | mcp.types.CreateTaskResult:
"""Delegate to child server's middleware.
"""Delegate to child server's read_resource().

Skips task routing at this layer - the child's template._read() will
check _task_metadata contextvar and submit to Docket if appropriate.
Expand All @@ -335,7 +292,6 @@ async def _read(
Only sets _docket_fn_key if not already set - in nested mounts, the
outermost wrapper sets the key and inner wrappers preserve it.
"""
import fastmcp.server.context
from fastmcp.server.dependencies import _docket_fn_key

# Expand the original template with params to get internal URI
Expand All @@ -352,21 +308,10 @@ async def _read(
if not existing_key or "{" not in existing_key:
key_token = _docket_fn_key.set(self.key)
try:
try:
from fastmcp.server.dependencies import get_context

get_context() # Will raise if no context
result = await self._server._read_resource_middleware(original_uri)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]
except RuntimeError:
# No context (e.g., Docket worker) - create one for the child server
async with fastmcp.server.context.Context(fastmcp=self._server):
result = await self._server._read_resource_middleware(original_uri)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]
result = await self._server.read_resource(original_uri)
if isinstance(result, mcp.types.CreateTaskResult):
return result
return result[0]
finally:
if key_token is not None:
_docket_fn_key.reset(key_token)
Expand Down Expand Up @@ -481,7 +426,7 @@ async def list_tools(self) -> Sequence[Tool]:
each tool as a FastMCPProviderTool that delegates execution to the
nested server's middleware.
"""
raw_tools = await self.server.get_tools(apply_middleware=True)
raw_tools = await self.server.get_tools(run_middleware=True)
return [FastMCPProviderTool.wrap(self.server, t) for t in raw_tools]

async def get_tool(self, name: str) -> Tool | None:
Expand All @@ -500,7 +445,7 @@ async def list_resources(self) -> Sequence[Resource]:
each resource as a FastMCPProviderResource that delegates reading to the
nested server's middleware.
"""
raw_resources = await self.server.get_resources(apply_middleware=True)
raw_resources = await self.server.get_resources(run_middleware=True)
return [FastMCPProviderResource.wrap(self.server, r) for r in raw_resources]

async def get_resource(self, uri: str) -> Resource | None:
Expand All @@ -518,7 +463,7 @@ async def list_resource_templates(self) -> Sequence[ResourceTemplate]:
Returns FastMCPProviderResourceTemplate instances that create
FastMCPProviderResources when materialized.
"""
raw_templates = await self.server.get_resource_templates(apply_middleware=True)
raw_templates = await self.server.get_resource_templates(run_middleware=True)
return [
FastMCPProviderResourceTemplate.wrap(self.server, t) for t in raw_templates
]
Expand All @@ -541,7 +486,7 @@ async def list_prompts(self) -> Sequence[Prompt]:
Returns FastMCPProviderPrompt instances that delegate rendering to the
wrapped server's middleware.
"""
raw_prompts = await self.server.get_prompts(apply_middleware=True)
raw_prompts = await self.server.get_prompts(run_middleware=True)
return [FastMCPProviderPrompt.wrap(self.server, p) for p in raw_prompts]

async def get_prompt(self, name: str) -> Prompt | None:
Expand Down
Loading
Loading