diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py index e6195a2d3..42223be29 100644 --- a/src/fastmcp/tools/tool.py +++ b/src/fastmcp/tools/tool.py @@ -185,8 +185,9 @@ class Tool(FastMCPComponent): tool: Tool, transform_fn: Callable[..., Any] | None = None, name: str | None = None, + title: str | None | NotSetT = NotSet, transform_args: dict[str, ArgTransform] | None = None, - description: str | None = None, + description: str | None | NotSetT = NotSet, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, output_schema: dict[str, Any] | None | Literal[False] = None, @@ -199,6 +200,7 @@ class Tool(FastMCPComponent): tool=tool, transform_fn=transform_fn, name=name, + title=title, transform_args=transform_args, description=description, tags=tags, diff --git a/src/fastmcp/tools/tool_transform.py b/src/fastmcp/tools/tool_transform.py index 88ae17495..09c89cc2f 100644 --- a/src/fastmcp/tools/tool_transform.py +++ b/src/fastmcp/tools/tool_transform.py @@ -325,7 +325,8 @@ class TransformedTool(Tool): cls, tool: Tool, name: str | None = None, - description: str | None = None, + title: str | None | NotSetT = NotSet, + description: str | None | NotSetT = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, @@ -342,6 +343,7 @@ class TransformedTool(Tool): to call the parent tool. Functions with **kwargs receive transformed argument names. name: New name for the tool. Defaults to parent tool's name. + title: New title for the tool. Defaults to parent tool's title. transform_args: Optional transformations for parent tool arguments. Only specified arguments are transformed, others pass through unchanged: - Simple rename (str) @@ -506,13 +508,18 @@ class TransformedTool(Tool): f"{', '.join(sorted(duplicates))}" ) - final_description = description if description is not None else tool.description + final_name = name or tool.name + final_description = ( + description if not isinstance(description, NotSetT) else tool.description + ) + final_title = title if not isinstance(title, NotSetT) else tool.title transformed_tool = cls( fn=final_fn, forwarding_fn=forwarding_fn, parent_tool=tool, - name=name or tool.name, + name=final_name, + title=final_title, description=final_description, parameters=final_schema, output_schema=final_output_schema, diff --git a/tests/tools/test_tool_transform.py b/tests/tools/test_tool_transform.py index 85fced3f9..e1f062394 100644 --- a/tests/tools/test_tool_transform.py +++ b/tests/tools/test_tool_transform.py @@ -1316,3 +1316,88 @@ class TestTransformToolOutputSchema: # Should use ToolResult content directly assert result.content[0].text == "Direct: 6" # type: ignore[attr-defined] assert result.structured_content == {"direct_value": 6, "doubled": 12} + + +@pytest.fixture +def sample_tool(): + """Sample tool for testing transformations.""" + + def sample_func(x: int) -> str: + return f"Result: {x}" + + return Tool.from_function( + sample_func, + name="sample_tool", + title="Original Tool Title", + description="Original description", + ) + + +@pytest.fixture +def sample_tool_no_title(): + """Sample tool without title for testing.""" + + def sample_func(x: int) -> str: + return f"Result: {x}" + + return Tool.from_function(sample_func, name="no_title_tool") + + +def test_transform_inherits_title(sample_tool): + """Test that transformed tools inherit title when none specified.""" + transformed = Tool.from_tool(sample_tool) + assert transformed.title == "Original Tool Title" + + +def test_transform_overrides_title(sample_tool): + """Test that transformed tools can override title.""" + transformed = Tool.from_tool(sample_tool, title="New Tool Title") + assert transformed.title == "New Tool Title" + + +def test_transform_sets_title_to_none(sample_tool): + """Test that transformed tools can explicitly set title to None.""" + transformed = Tool.from_tool(sample_tool, title=None) + assert transformed.title is None + + +def test_transform_inherits_none_title(sample_tool_no_title): + """Test that transformed tools inherit None title.""" + transformed = Tool.from_tool(sample_tool_no_title) + assert transformed.title is None + + +def test_transform_adds_title_to_none(sample_tool_no_title): + """Test that transformed tools can add title when parent has None.""" + transformed = Tool.from_tool(sample_tool_no_title, title="Added Title") + assert transformed.title == "Added Title" + + +def test_transform_inherits_description(sample_tool): + """Test that transformed tools inherit description when none specified.""" + transformed = Tool.from_tool(sample_tool) + assert transformed.description == "Original description" + + +def test_transform_overrides_description(sample_tool): + """Test that transformed tools can override description.""" + transformed = Tool.from_tool(sample_tool, description="New description") + assert transformed.description == "New description" + + +def test_transform_sets_description_to_none(sample_tool): + """Test that transformed tools can explicitly set description to None.""" + transformed = Tool.from_tool(sample_tool, description=None) + assert transformed.description is None + + +def test_transform_inherits_none_description(sample_tool_no_title): + """Test that transformed tools inherit None description.""" + transformed = Tool.from_tool(sample_tool_no_title) + assert transformed.description is None + + +def test_transform_adds_description_to_none(sample_tool_no_title): + """Test that transformed tools can add description when parent has None.""" + transformed = Tool.from_tool(sample_tool_no_title, description="Added description") + assert transformed.description == "Added description"