mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 13:04:18 +02:00
Remove deprecated exclude_args tool parameter
This commit is contained in:
parent
7f032bb82e
commit
45475bd072
7 changed files with 5 additions and 187 deletions
|
|
@ -343,7 +343,6 @@ def extract_components(module: ModuleType) -> list[FastMCPComponent]:
|
|||
annotations=meta.annotations,
|
||||
meta=meta.meta,
|
||||
task=resolved_task,
|
||||
exclude_args=meta.exclude_args,
|
||||
timeout=meta.timeout,
|
||||
auth=meta.auth,
|
||||
run_in_thread=meta.run_in_thread,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,6 @@ class ToolDecoratorMixin:
|
|||
annotations=fmeta.annotations,
|
||||
meta=tool_meta,
|
||||
task=resolved_task,
|
||||
exclude_args=fmeta.exclude_args,
|
||||
timeout=fmeta.timeout,
|
||||
auth=fmeta.auth,
|
||||
run_in_thread=fmeta.run_in_thread,
|
||||
|
|
@ -184,7 +183,6 @@ class ToolDecoratorMixin:
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -206,7 +204,6 @@ class ToolDecoratorMixin:
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -215,10 +212,9 @@ class ToolDecoratorMixin:
|
|||
run_in_thread: bool = True,
|
||||
) -> Callable[[F], F]: ...
|
||||
|
||||
# NOTE: This method mirrors fastmcp.tools.tool() but adds registration,
|
||||
# the `enabled` param, and supports the deprecated `exclude_args` param.
|
||||
# When deprecated params are removed, this should delegate to the standalone
|
||||
# decorator to reduce duplication.
|
||||
# NOTE: This method mirrors fastmcp.tools.tool() but adds registration and
|
||||
# the `enabled` param. It could delegate to the standalone decorator to
|
||||
# reduce duplication.
|
||||
def tool(
|
||||
self: LocalProvider,
|
||||
name_or_fn: str | AnyFunction | None = None,
|
||||
|
|
@ -231,7 +227,6 @@ class ToolDecoratorMixin:
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool = True,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -261,7 +256,6 @@ class ToolDecoratorMixin:
|
|||
tags: Optional set of tags for categorizing the tool
|
||||
output_schema: Optional JSON schema for the tool's output
|
||||
annotations: Optional annotations about the tool's behavior
|
||||
exclude_args: Optional list of argument names to exclude from the tool schema
|
||||
meta: Optional meta information about the tool
|
||||
enabled: Whether the tool is enabled (default True). If False, adds to blocklist.
|
||||
task: Optional task configuration for background execution
|
||||
|
|
@ -327,7 +321,6 @@ class ToolDecoratorMixin:
|
|||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
task=resolved_task,
|
||||
timeout=timeout,
|
||||
|
|
@ -353,7 +346,6 @@ class ToolDecoratorMixin:
|
|||
annotations=annotations,
|
||||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
enabled=enabled,
|
||||
|
|
@ -394,7 +386,6 @@ class ToolDecoratorMixin:
|
|||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
enabled=enabled,
|
||||
task=task,
|
||||
|
|
|
|||
|
|
@ -1716,7 +1716,6 @@ class FastMCP(
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
app: AppConfig | dict[str, Any] | bool | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -1738,7 +1737,6 @@ class FastMCP(
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
app: AppConfig | dict[str, Any] | bool | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -1759,7 +1757,6 @@ class FastMCP(
|
|||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
app: AppConfig | dict[str, Any] | bool | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -1791,8 +1788,6 @@ class FastMCP(
|
|||
tags: Optional set of tags for categorizing the tool
|
||||
output_schema: Optional JSON schema for the tool's output
|
||||
annotations: Optional annotations about the tool's behavior
|
||||
exclude_args: Optional list of argument names to exclude from the tool schema.
|
||||
Deprecated: Use `Depends()` for dependency injection instead.
|
||||
meta: Optional meta information about the tool
|
||||
|
||||
Examples:
|
||||
|
|
@ -1838,7 +1833,6 @@ class FastMCP(
|
|||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
task=task if task is not None else self._support_tasks_by_default,
|
||||
timeout=timeout,
|
||||
|
|
|
|||
|
|
@ -256,7 +256,6 @@ class Tool(FastMCPComponent):
|
|||
icons: list[Icon] | None = None,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -276,7 +275,6 @@ class Tool(FastMCPComponent):
|
|||
icons=icons,
|
||||
tags=tags,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
output_schema=output_schema,
|
||||
meta=meta,
|
||||
task=task,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from fastmcp.utilities.types import (
|
|||
Audio,
|
||||
File,
|
||||
Image,
|
||||
create_function_without_params,
|
||||
get_cached_typeadapter,
|
||||
is_class_member_of_type,
|
||||
replace_type,
|
||||
|
|
@ -178,7 +177,6 @@ class ParsedFunction:
|
|||
def from_function(
|
||||
cls,
|
||||
fn: Callable[..., Any],
|
||||
exclude_args: list[str] | None = None,
|
||||
validate: bool = True,
|
||||
wrap_non_object_output_schema: bool = True,
|
||||
) -> ParsedFunction:
|
||||
|
|
@ -193,19 +191,6 @@ class ParsedFunction:
|
|||
"Functions with **kwargs are not supported as tools"
|
||||
)
|
||||
|
||||
# Reject exclude_args that don't exist in the function or don't have a default value
|
||||
if exclude_args:
|
||||
for arg_name in exclude_args:
|
||||
if arg_name not in sig.parameters:
|
||||
raise ValueError(
|
||||
f"Parameter '{arg_name}' in exclude_args does not exist in function."
|
||||
)
|
||||
param = sig.parameters[arg_name]
|
||||
if param.default == inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"Parameter '{arg_name}' in exclude_args must have a default value."
|
||||
)
|
||||
|
||||
# collect name and description before we potentially modify the function
|
||||
fn_name = getattr(fn, "__name__", None) or fn.__class__.__name__
|
||||
outer_docstring = parse_docstring(fn)
|
||||
|
|
@ -241,19 +226,10 @@ class ParsedFunction:
|
|||
# Handle injected parameters (Context, Docket dependencies)
|
||||
wrapper_fn = without_injected_parameters(fn)
|
||||
|
||||
# Also handle exclude_args with non-serializable types (issue #2431)
|
||||
# This must happen before Pydantic tries to serialize the parameters
|
||||
if exclude_args:
|
||||
wrapper_fn = create_function_without_params(wrapper_fn, list(exclude_args))
|
||||
|
||||
input_type_adapter = get_cached_typeadapter(wrapper_fn)
|
||||
input_schema = input_type_adapter.json_schema()
|
||||
|
||||
# Compress and handle exclude_args
|
||||
prune_params = list(exclude_args) if exclude_args else None
|
||||
input_schema = compress_schema(
|
||||
input_schema, prune_params=prune_params, prune_titles=True
|
||||
)
|
||||
input_schema = compress_schema(input_schema, prune_titles=True)
|
||||
|
||||
# Inject parameter descriptions from the docstring into the schema.
|
||||
# Explicit annotations (Field(description=...), Annotated[x, "..."])
|
||||
|
|
|
|||
|
|
@ -169,7 +169,6 @@ class ToolMeta:
|
|||
meta: dict[str, Any] | None = None
|
||||
app: Any = None
|
||||
task: bool | TaskConfig | None = None
|
||||
exclude_args: list[str] | None = None
|
||||
timeout: float | None = None
|
||||
auth: AuthCheck | list[AuthCheck] | None = None
|
||||
enabled: bool = True
|
||||
|
|
@ -234,7 +233,6 @@ class FunctionTool(Tool):
|
|||
icons: list[Icon] | None = None,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
|
|
@ -271,7 +269,6 @@ class FunctionTool(Tool):
|
|||
]
|
||||
)
|
||||
or output_schema is not NotSet
|
||||
or exclude_args is not None
|
||||
)
|
||||
|
||||
if metadata is not None and individual_params_provided:
|
||||
|
|
@ -298,22 +295,12 @@ class FunctionTool(Tool):
|
|||
annotations=annotations,
|
||||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=True if run_in_thread is None else run_in_thread,
|
||||
)
|
||||
|
||||
if metadata.exclude_args and fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"The `exclude_args` parameter is deprecated as of FastMCP 2.14. "
|
||||
"Use dependency injection with `Depends()` instead for better lifecycle management. "
|
||||
"See https://gofastmcp.com/servers/dependency-injection#using-depends for examples.",
|
||||
FastMCPDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
parsed_fn = ParsedFunction.from_function(fn, exclude_args=metadata.exclude_args)
|
||||
parsed_fn = ParsedFunction.from_function(fn)
|
||||
func_name = metadata.name or parsed_fn.name
|
||||
|
||||
if func_name == "<lambda>":
|
||||
|
|
@ -591,7 +578,6 @@ def tool(
|
|||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -610,7 +596,6 @@ def tool(
|
|||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -630,7 +615,6 @@ def tool(
|
|||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
task: bool | TaskConfig | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
timeout: float | None = None,
|
||||
auth: AuthCheck | list[AuthCheck] | None = None,
|
||||
run_in_thread: bool = True,
|
||||
|
|
@ -672,7 +656,6 @@ def tool(
|
|||
annotations=annotations,
|
||||
meta=meta,
|
||||
task=resolve_task_config(task),
|
||||
exclude_args=exclude_args,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=run_in_thread,
|
||||
|
|
@ -691,7 +674,6 @@ def tool(
|
|||
annotations=annotations,
|
||||
meta=meta,
|
||||
task=task,
|
||||
exclude_args=exclude_args,
|
||||
timeout=timeout,
|
||||
auth=auth,
|
||||
run_in_thread=run_in_thread,
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from mcp.server.session import ServerSession
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.tools.base import Tool
|
||||
|
||||
|
||||
async def test_tool_exclude_args():
|
||||
"""Test that tool args are excluded."""
|
||||
mcp = FastMCP("Test Server")
|
||||
|
||||
@mcp.tool(exclude_args=["state"])
|
||||
def echo(message: str, state: dict[str, Any] | None = None) -> str:
|
||||
"""Echo back the message provided."""
|
||||
if state:
|
||||
# State was read
|
||||
pass
|
||||
return message
|
||||
|
||||
tools = await mcp.list_tools()
|
||||
assert len(tools) == 1
|
||||
assert "state" not in tools[0].parameters["properties"]
|
||||
|
||||
|
||||
async def test_tool_exclude_args_without_default_value_raises_error():
|
||||
"""Test that excluding args without default values raises ValueError"""
|
||||
mcp = FastMCP("Test Server")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@mcp.tool(exclude_args=["state"])
|
||||
def echo(message: str, state: dict[str, Any] | None) -> str:
|
||||
"""Echo back the message provided."""
|
||||
if state:
|
||||
# State was read
|
||||
pass
|
||||
return message
|
||||
|
||||
|
||||
async def test_add_tool_method_exclude_args():
|
||||
"""Test that tool exclude_args work with the add_tool method."""
|
||||
mcp = FastMCP("Test Server")
|
||||
|
||||
def create_item(
|
||||
name: str, value: int, state: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Create a new item."""
|
||||
if state:
|
||||
# State was read
|
||||
pass
|
||||
return {"name": name, "value": value}
|
||||
|
||||
tool = Tool.from_function(
|
||||
create_item,
|
||||
name="create_item",
|
||||
exclude_args=["state"],
|
||||
)
|
||||
mcp.add_tool(tool)
|
||||
|
||||
# Check tool via public API
|
||||
tools = await mcp.list_tools()
|
||||
assert len(tools) == 1
|
||||
assert "state" not in tools[0].parameters["properties"]
|
||||
|
||||
|
||||
async def test_tool_functionality_with_exclude_args():
|
||||
"""Test that tool functionality is preserved when using exclude_args."""
|
||||
mcp = FastMCP("Test Server")
|
||||
|
||||
def create_item(
|
||||
name: str, value: int, state: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Create a new item."""
|
||||
if state:
|
||||
# state was read
|
||||
pass
|
||||
return {"name": name, "value": value}
|
||||
|
||||
tool = Tool.from_function(
|
||||
create_item,
|
||||
name="create_item",
|
||||
exclude_args=["state"],
|
||||
)
|
||||
mcp.add_tool(tool)
|
||||
|
||||
# Use the tool to verify functionality is preserved
|
||||
async with Client(mcp) as client:
|
||||
result = await client.call_tool(
|
||||
"create_item", {"name": "test_item", "value": 42}
|
||||
)
|
||||
assert result.data == {"name": "test_item", "value": 42}
|
||||
|
||||
|
||||
async def test_exclude_args_with_non_serializable_type():
|
||||
"""Test that exclude_args works even when the excluded parameter type can't be serialized.
|
||||
|
||||
This test ensures that exclude_args works correctly when the excluded parameter
|
||||
has a type that Pydantic cannot serialize (like ServerSession). The bug was that
|
||||
get_cached_typeadapter would try to serialize all parameters before compress_schema
|
||||
could exclude them, causing a PydanticSchemaGenerationError.
|
||||
"""
|
||||
|
||||
def my_tool(message: str, session: ServerSession | None = None) -> str:
|
||||
"""A tool that takes a non-serializable Session parameter."""
|
||||
return message
|
||||
|
||||
# This should not raise an error even though ServerSession can't be serialized
|
||||
tool = Tool.from_function(
|
||||
my_tool,
|
||||
name="my_tool",
|
||||
exclude_args=["session"],
|
||||
)
|
||||
|
||||
# Verify the tool was created successfully
|
||||
assert tool is not None
|
||||
assert tool.name == "my_tool"
|
||||
|
||||
# Verify the session parameter is excluded from the schema
|
||||
assert "session" not in tool.parameters["properties"]
|
||||
assert "message" in tool.parameters["properties"]
|
||||
Loading…
Add table
Add a link
Reference in a new issue