TransformedTool has four related issues in src/fastmcp/tools/tool_transform.py:
1. run() crashes on sync transform_fn (line 313)
run() unconditionally does await self.fn(**arguments). When transform_fn is a sync function, this raises TypeError: object str can't be used in 'await' expression. FunctionTool.run() handles this correctly with is_coroutine_function + call_sync_fn_in_threadpool — TransformedTool.run() should do the same.
from fastmcp.tools.function_tool import FunctionTool
from fastmcp.tools.tool_transform import TransformedTool
def greet(name: str) -> str:
return f"Hello, {name}!"
def upper_greet(name: str) -> str:
return f"HELLO, {name}!"
parent = FunctionTool.from_function(greet)
transformed = TransformedTool.from_tool(parent, transform_fn=upper_greet)
import asyncio
asyncio.run(transformed.run({"name": "World"}))
# TypeError: object str can't be used in 'await' expression
2. from_tool() shallow-copies property schemas (line 634)
parent_props = parent_tool.parameters.get("properties", {}).copy() is a shallow copy. Nested schema dicts (like {"items": {"type": "string"}} inside an array property) are shared references between parent and transformed tool. Mutating the transformed schema corrupts the parent.
from fastmcp.tools.function_tool import FunctionTool
from fastmcp.tools.tool_transform import TransformedTool, ArgTransform
def my_tool(items: list[str], count: int = 5) -> str:
return f"{items[:count]}"
parent = FunctionTool.from_function(my_tool)
transformed = TransformedTool.from_tool(
parent, transform_args={"items": ArgTransform(name="data")}
)
# Nested schemas are shared
print(parent.parameters["properties"]["items"]["items"]
is transformed.parameters["properties"]["data"]["items"]) # True
# Mutating transformed corrupts parent
transformed.parameters["properties"]["data"]["items"]["type"] = "integer"
print(parent.parameters["properties"]["items"]["items"]["type"]) # "integer" — corrupted
$defs is already deepcopy'd (line 633) but properties is not.
3. from_tool() crashes with str/None in transform_args (line 644–651)
The docstring (lines 388–391) documents that transform_args values can be str (simple rename) or None (drop argument), but line 645 assigns the raw value to transform and line 651 calls transform.hide, which crashes with AttributeError: 'str' object has no attribute 'hide'.
from fastmcp.tools.function_tool import FunctionTool
from fastmcp.tools.tool_transform import TransformedTool
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
parent = FunctionTool.from_function(greet)
TransformedTool.from_tool(parent, transform_args={"name": "username"})
# AttributeError: 'str' object has no attribute 'hide'
TransformedTool.from_tool(parent, transform_args={"greeting": None})
# AttributeError: 'NoneType' object has no attribute 'hide'
Needs normalization: str → ArgTransform(name=value), None → ArgTransform(hide=True).
4. output_schema=False rejected despite being documented (line 398)
The docstring says output_schema=False disables output schema and structured outputs, but passing it raises a Pydantic validation error because the model only accepts dict | None.
🤖 Generated with Claude Code
TransformedToolhas four related issues insrc/fastmcp/tools/tool_transform.py:1.
run()crashes on synctransform_fn(line 313)run()unconditionally doesawait self.fn(**arguments). Whentransform_fnis a sync function, this raisesTypeError: object str can't be used in 'await' expression.FunctionTool.run()handles this correctly withis_coroutine_function+call_sync_fn_in_threadpool—TransformedTool.run()should do the same.2.
from_tool()shallow-copies property schemas (line 634)parent_props = parent_tool.parameters.get("properties", {}).copy()is a shallow copy. Nested schema dicts (like{"items": {"type": "string"}}inside an array property) are shared references between parent and transformed tool. Mutating the transformed schema corrupts the parent.$defsis alreadydeepcopy'd (line 633) butpropertiesis not.3.
from_tool()crashes with str/None intransform_args(line 644–651)The docstring (lines 388–391) documents that
transform_argsvalues can bestr(simple rename) orNone(drop argument), but line 645 assigns the raw value totransformand line 651 callstransform.hide, which crashes withAttributeError: 'str' object has no attribute 'hide'.Needs normalization:
str→ArgTransform(name=value),None→ArgTransform(hide=True).4.
output_schema=Falserejected despite being documented (line 398)The docstring says
output_schema=Falsedisables output schema and structured outputs, but passing it raises a Pydantic validation error because the model only acceptsdict | None.🤖 Generated with Claude Code