fix: preserve tool decorator metadata (#4072)

Generated with Codex.

Co-authored-by: lawrence3699 <lawrence3699@users.noreply.github.com>
This commit is contained in:
chaoliang yan 2026-05-05 02:22:31 +10:00 committed by GitHub
commit 2ffe68cfa1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 2 deletions

View file

@ -24,7 +24,7 @@ from pydantic import Field
from pydantic.json_schema import SkipJsonSchema
import fastmcp
from fastmcp.decorators import resolve_task_config
from fastmcp.decorators import get_fastmcp_meta, resolve_task_config
from fastmcp.exceptions import FastMCPDeprecationWarning
from fastmcp.server.auth.authorization import AuthCheck
from fastmcp.server.dependencies import without_injected_parameters
@ -169,6 +169,11 @@ class FunctionTool(Tool):
"Use metadata alone or individual parameters alone."
)
if metadata is None and not individual_params_provided:
fmeta = get_fastmcp_meta(fn)
if isinstance(fmeta, ToolMeta):
metadata = fmeta
# Build metadata from kwargs if not provided
if metadata is None:
metadata = ToolMeta(

View file

@ -12,7 +12,8 @@ import pytest
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.tools import tool
from fastmcp.tools.function_tool import DecoratedTool, ToolMeta
from fastmcp.tools.base import Tool
from fastmcp.tools.function_tool import DecoratedTool, FunctionTool, ToolMeta
class TestToolDecorator:
@ -89,6 +90,33 @@ class TestToolDecorator:
assert decorated.__fastmcp__.tags == {"greeting", "demo"}
assert decorated.__fastmcp__.meta == {"custom": "value"}
@pytest.mark.parametrize(
"factory", [Tool.from_function, FunctionTool.from_function]
)
def test_from_function_preserves_decorator_metadata(self, factory):
"""Direct from_function calls should respect @tool metadata."""
@tool(
name="custom-greet",
version="v1",
title="Greeting Tool",
description="Greets people",
tags={"greeting", "demo"},
meta={"custom": "value"},
)
def greet(name: str) -> str:
"""Fallback description."""
return f"Hello, {name}!"
created_tool = factory(greet)
assert created_tool.name == "custom-greet"
assert created_tool.version == "v1"
assert created_tool.title == "Greeting Tool"
assert created_tool.description == "Greets people"
assert created_tool.tags == {"greeting", "demo"}
assert created_tool.meta == {"custom": "value"}
async def test_tool_function_still_callable(self):
"""Decorated function should still be directly callable."""