Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions fastmcp_slim/fastmcp/tools/tool_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,8 @@ def _create_forwarding_transform(
schema = {
"type": "object",
"properties": new_props,
"required": list(new_required),
# Iterate props (not the set) for deterministic ordering
"required": [p for p in new_props if p in new_required],
"additionalProperties": False,
}

Expand Down Expand Up @@ -899,7 +900,11 @@ def _merge_schema_with_precedence(
result = {
"type": "object",
"properties": merged_props,
"required": list(final_required),
# Iterate props (not the set) for deterministic ordering; keep any
# required names not present in properties (sorted) rather than
# silently dropping them.
"required": [p for p in merged_props if p in final_required]
+ sorted(final_required - set(merged_props)),
"additionalProperties": False,
}

Expand Down
17 changes: 17 additions & 0 deletions tests/tools/tool_transform/test_tool_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ def test_tool_from_tool_no_change(add_tool):
assert new_tool.description == add_tool.description


def test_transformed_tool_required_order_is_deterministic():
"""`required` must follow property order, not set iteration order.

Set iteration order varies with PYTHONHASHSEED, which broke snapshot
tests of tools/list output across processes.
"""

def fn(alpha: int, beta: str, gamma: float, delta: bool, epsilon: int) -> str:
return "x"

base = Tool.from_function(fn)
transformed = Tool.from_tool(base, transform_args={"alpha": ArgTransform(name="a")})
props = list(transformed.parameters["properties"])
assert transformed.parameters["required"] == props
assert props == ["a", "beta", "gamma", "delta", "epsilon"]


def test_from_tool_accepts_decorated_function():
@tool
def search(q: str, limit: int = 10) -> list[str]:
Expand Down
Loading