Skip to content

Commit 047664f

Browse files
committed
Add decorator parameter for immediate return value in LRO
1 parent b802dc4 commit 047664f

File tree

2 files changed

+41
-29
lines changed

2 files changed

+41
-29
lines changed

src/mcp/server/fastmcp/tools/base.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import functools
44
import inspect
5-
from collections.abc import Callable
5+
from collections.abc import Awaitable, Callable
66
from functools import cached_property
77
from typing import TYPE_CHECKING, Any, Literal
88

@@ -39,6 +39,9 @@ class Tool(BaseModel):
3939
default=["sync"], description="Supported invocation modes (sync/async)"
4040
)
4141
meta: dict[str, Any] | None = Field(description="Optional additional tool information.", default=None)
42+
immediate_result: Callable[..., Awaitable[list[Any]]] | None = Field(
43+
None, exclude=True, description="Optional immediate result function for async tools"
44+
)
4245

4346
@cached_property
4447
def output_schema(self) -> dict[str, Any] | None:
@@ -55,7 +58,9 @@ def from_function(
5558
annotations: ToolAnnotations | None = None,
5659
structured_output: bool | None = None,
5760
invocation_modes: list[InvocationMode] | None = None,
61+
keep_alive: int | None = None,
5862
meta: dict[str, Any] | None = None,
63+
immediate_result: Callable[..., Awaitable[list[Any]]] | None = None,
5964
) -> Tool:
6065
"""Create a Tool from a function."""
6166
func_name = name or fn.__name__
@@ -80,6 +85,39 @@ def from_function(
8085
if invocation_modes is None:
8186
invocation_modes = ["sync"]
8287

88+
# Set appropriate default keep_alive based on async compatibility
89+
# if user didn't specify custom keep_alive
90+
if keep_alive is None and "async" in invocation_modes:
91+
keep_alive = 3600 # Default for async-compatible tools
92+
93+
# Validate keep_alive is only used with async-compatible tools
94+
if keep_alive is not None and "async" not in invocation_modes:
95+
raise ValueError(
96+
f"keep_alive parameter can only be used with async-compatible tools. "
97+
f"Tool '{func_name}' has invocation_modes={invocation_modes} "
98+
f"but specifies keep_alive={keep_alive}. "
99+
f"Add 'async' to invocation_modes to use keep_alive."
100+
)
101+
102+
# Process meta dictionary and add keep_alive if specified
103+
meta = meta or {}
104+
if keep_alive is not None:
105+
meta = meta.copy() # Don't modify the original dict
106+
meta["_keep_alive"] = keep_alive
107+
108+
# Validate immediate_result usage
109+
if immediate_result is not None:
110+
# Check if tool supports async invocation
111+
if "async" not in invocation_modes:
112+
raise ValueError(
113+
"immediate_result can only be used with async-compatible tools. "
114+
"Add 'async' to invocation_modes to use immediate_result."
115+
)
116+
117+
# Validate that immediate_result is an async callable
118+
if not _is_async_callable(immediate_result):
119+
raise ValueError("immediate_result must be an async callable that returns list[ContentBlock]")
120+
83121
return cls(
84122
fn=fn,
85123
name=func_name,
@@ -92,6 +130,7 @@ def from_function(
92130
annotations=annotations,
93131
invocation_modes=invocation_modes,
94132
meta=meta,
133+
immediate_result=immediate_result,
95134
)
96135

97136
async def run(

src/mcp/server/fastmcp/tools/tool_manager.py

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,34 +55,6 @@ def add_tool(
5555
meta: dict[str, Any] | None = None,
5656
) -> Tool:
5757
"""Add a tool to the server."""
58-
# Default to sync mode if no invocation modes specified
59-
if invocation_modes is None:
60-
invocation_modes = ["sync"]
61-
62-
# Set appropriate default keep_alive based on async compatibility
63-
# if user didn't specify custom keep_alive
64-
if keep_alive is None and "async" in invocation_modes:
65-
keep_alive = 3600 # Default for async-compatible tools
66-
67-
# Validate keep_alive is only used with async-compatible tools
68-
if keep_alive is not None and "async" not in invocation_modes:
69-
raise ValueError(
70-
f"keep_alive parameter can only be used with async-compatible tools. "
71-
f"Tool '{name or fn.__name__}' has invocation_modes={invocation_modes} "
72-
f"but specifies keep_alive={keep_alive}. "
73-
f"Add 'async' to invocation_modes to use keep_alive."
74-
)
75-
76-
meta = meta or {}
77-
if keep_alive is not None:
78-
meta.update(
79-
{
80-
# default keepalive value is stashed in _meta to pass it to the lowlevel Server
81-
# without adding it to the actual protocol-level tool definition
82-
"_keep_alive": keep_alive
83-
}
84-
)
85-
8658
tool = Tool.from_function(
8759
fn,
8860
name=name,
@@ -91,6 +63,7 @@ def add_tool(
9163
annotations=annotations,
9264
structured_output=structured_output,
9365
invocation_modes=invocation_modes,
66+
keep_alive=keep_alive,
9467
meta=meta,
9568
)
9669
existing = self._tools.get(tool.name)

0 commit comments

Comments
 (0)