Fix title field preservation in tool transformations

Add title parameter to Tool.from_tool() and TransformedTool.from_tool()
to properly inherit or override title field when transforming tools.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jeremiah Lowin 2025-07-12 09:53:49 -04:00
commit 9326cb91db
3 changed files with 98 additions and 4 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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"