Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 7 additions & 35 deletions src/fastmcp/server/low_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
from mcp.shared.session import RequestResponder
from pydantic import AnyUrl

from fastmcp.prompts import Prompt
from fastmcp.prompts.prompt import PromptResult
from fastmcp.server.dependencies import _docket_fn_key, _task_metadata
from fastmcp.utilities.logging import get_logger

if TYPE_CHECKING:
Expand Down Expand Up @@ -254,20 +251,19 @@ def get_prompt(
[
Callable[
[str, dict[str, Any] | None],
Awaitable[PromptResult | mcp.types.CreateTaskResult],
Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
]
],
Callable[
[str, dict[str, Any] | None],
Awaitable[PromptResult | mcp.types.CreateTaskResult],
Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
],
]:
"""
Decorator for registering a get_prompt handler with CreateTaskResult support.

The MCP SDK's get_prompt decorator does not support returning CreateTaskResult
for background task execution. This decorator provides that support by wrapping the
handler with task metadata extraction, contextvar management, and MCP format conversion.
for background task execution. This decorator wraps the result in ServerResult.

This decorator can be removed once the MCP SDK adds native CreateTaskResult support
for prompts.
Expand All @@ -276,41 +272,17 @@ def get_prompt(
def decorator(
func: Callable[
[str, dict[str, Any] | None],
Awaitable[PromptResult | mcp.types.CreateTaskResult],
Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
],
) -> Callable[
[str, dict[str, Any] | None],
Awaitable[PromptResult | mcp.types.CreateTaskResult],
Awaitable[mcp.types.GetPromptResult | mcp.types.CreateTaskResult],
]:
async def handler(
req: mcp.types.GetPromptRequest,
) -> mcp.types.ServerResult:
name = req.params.name
arguments = req.params.arguments

# Extract task metadata from request context
task_meta_dict: dict[str, Any] | None = None
try:
ctx = self.request_context
if ctx.experimental.is_task:
task_meta = ctx.experimental.task_metadata
task_meta_dict = task_meta.model_dump(exclude_none=True)
except (AttributeError, LookupError):
pass

# Set contextvars
task_token = _task_metadata.set(task_meta_dict)
key_token = _docket_fn_key.set(Prompt.make_key(name))
try:
result = await func(name, arguments)

if isinstance(result, mcp.types.CreateTaskResult):
return mcp.types.ServerResult(result)

return mcp.types.ServerResult(result.to_mcp_prompt_result())
finally:
_task_metadata.reset(task_token)
_docket_fn_key.reset(key_token)
result = await func(req.params.name, req.params.arguments)
return mcp.types.ServerResult(result)

self.request_handlers[mcp.types.GetPromptRequest] = handler
return func
Expand Down
38 changes: 34 additions & 4 deletions src/fastmcp/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1584,18 +1584,48 @@ async def _read_resource_mcp(

async def _get_prompt_mcp(
self, name: str, arguments: dict[str, Any] | None
) -> PromptResult | mcp.types.CreateTaskResult:
) -> mcp.types.GetPromptResult | mcp.types.CreateTaskResult:
"""Handle MCP 'getPrompt' requests.

The LowLevelServer.get_prompt() decorator handles task metadata,
contextvars, and MCP conversion.
Sets task metadata contextvar and calls render_prompt(). The prompt's
_render() method handles the backgrounding decision.

Args:
name: The prompt name
arguments: Prompt arguments

Returns:
GetPromptResult or CreateTaskResult for background execution
"""
from fastmcp.server.dependencies import _docket_fn_key, _task_metadata

logger.debug(
f"[{self.name}] Handler called: get_prompt %s with %s", name, arguments
)

try:
return await self.render_prompt(name, arguments)
# Extract SEP-1686 task metadata from request context
task_meta_dict: dict[str, Any] | None = None
try:
ctx = self._mcp_server.request_context
if ctx.experimental.is_task:
task_meta = ctx.experimental.task_metadata
task_meta_dict = task_meta.model_dump(exclude_none=True)
except (AttributeError, LookupError):
pass

# Set contextvars so prompt._render() can access them
task_token = _task_metadata.set(task_meta_dict)
key_token = _docket_fn_key.set(Prompt.make_key(name))
try:
result = await self.render_prompt(name, arguments)

if isinstance(result, mcp.types.CreateTaskResult):
return result
return result.to_mcp_prompt_result()
finally:
_task_metadata.reset(task_token)
_docket_fn_key.reset(key_token)
except DisabledError as e:
raise NotFoundError(f"Unknown prompt: {name!r}") from e
except NotFoundError:
Expand Down
Loading