Decorators return functions instead of component objects (#2856)

This commit is contained in:
Jeremiah Lowin 2026-01-12 21:58:07 -05:00 committed by GitHub
commit 1b723f302d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 2406 additions and 1606 deletions

View file

@ -21,7 +21,8 @@ from pydantic import AnyUrl, BaseModel
from fastmcp import Context, FastMCP
from fastmcp.client.client import CallToolResult, Client
from fastmcp.client.transports import FastMCPTransport
from fastmcp.prompts.prompt import FunctionPrompt, Message, Prompt
from fastmcp.prompts.function_prompt import FunctionPrompt
from fastmcp.prompts.prompt import Message, Prompt
from fastmcp.resources.resource import Resource
from fastmcp.server.middleware.caching import (
CachableToolResult,

View file

@ -16,7 +16,8 @@ from fastmcp.server.middleware.tool_injection import (
ResourceToolMiddleware,
ToolInjectionMiddleware,
)
from fastmcp.tools.tool import FunctionTool, Tool
from fastmcp.tools.function_tool import FunctionTool
from fastmcp.tools.tool import Tool
def multiply_fn(a: int, b: int) -> int:

View file

@ -10,7 +10,7 @@ from mcp.types import TextContent
from fastmcp import Client, Context, FastMCP
from fastmcp.exceptions import NotFoundError
from fastmcp.prompts.prompt import FunctionPrompt, Prompt
from fastmcp.prompts.prompt import Prompt
class TestPromptContext:
@ -247,6 +247,10 @@ class TestPromptDecorator:
async def test_prompt_direct_function_call(self):
"""Test that prompts can be registered via direct function call."""
from typing import cast
from fastmcp.prompts.function_prompt import DecoratedPrompt
mcp = FastMCP()
def standalone_function() -> str:
@ -255,11 +259,16 @@ class TestPromptDecorator:
result_fn = mcp.prompt(standalone_function, name="direct_call_prompt")
assert isinstance(result_fn, FunctionPrompt)
# In new decorator mode, returns the function with metadata
decorated = cast(DecoratedPrompt, result_fn)
assert hasattr(result_fn, "__fastmcp__")
assert decorated.__fastmcp__.name == "direct_call_prompt"
assert result_fn is standalone_function
prompts = await mcp.get_prompts()
prompt = next(p for p in prompts if p.name == "direct_call_prompt")
assert prompt is result_fn
# Prompt is registered separately, not same object as decorated function
assert prompt.name == "direct_call_prompt"
result = await mcp.render_prompt("direct_call_prompt")
assert len(result.messages) == 1

View file

@ -793,6 +793,10 @@ class TestToolOutputSchema:
assert result.structured_content is None
async def test_manual_structured_content(self):
from typing import cast
from fastmcp.tools.function_tool import DecoratedTool
mcp = FastMCP()
@mcp.tool
@ -801,7 +805,12 @@ class TestToolOutputSchema:
content="Hello, world!", structured_content={"message": "Hello, world!"}
)
assert f.output_schema is None
# In new decorator mode, check metadata instead of attributes
from fastmcp.utilities.types import NotSet
decorated = cast(DecoratedTool, f)
assert hasattr(f, "__fastmcp__")
assert decorated.__fastmcp__.output_schema is NotSet
result = await mcp.call_tool("f", {})
assert isinstance(result.content, list)
@ -1312,7 +1321,9 @@ class TestToolDecorator:
async def test_tool_direct_function_call(self):
"""Test that tools can be registered via direct function call."""
from fastmcp.tools import FunctionTool
from typing import cast
from fastmcp.tools.function_tool import DecoratedTool
mcp = FastMCP()
@ -1322,11 +1333,16 @@ class TestToolDecorator:
result_fn = mcp.tool(standalone_function, name="direct_call_tool")
assert isinstance(result_fn, FunctionTool)
# In new decorator mode, returns the function with metadata
decorated = cast(DecoratedTool, result_fn)
assert hasattr(result_fn, "__fastmcp__")
assert decorated.__fastmcp__.name == "direct_call_tool"
assert result_fn is standalone_function
tools = await mcp.get_tools()
tool = next(t for t in tools if t.name == "direct_call_tool")
assert tool is result_fn
# Tool is registered separately, not same object as decorated function
assert tool.name == "direct_call_tool"
result = await mcp.call_tool("direct_call_tool", {"x": 5, "y": 3})
assert result.structured_content == {"result": 8}

View file

@ -8,9 +8,9 @@ ValueError when task=True is used with a sync function.
import pytest
from fastmcp import FastMCP
from fastmcp.prompts.prompt import FunctionPrompt
from fastmcp.resources.resource import FunctionResource
from fastmcp.tools.tool import FunctionTool
from fastmcp.prompts.function_prompt import FunctionPrompt
from fastmcp.resources.function_resource import FunctionResource
from fastmcp.tools.function_tool import FunctionTool
async def test_sync_tool_with_explicit_task_true_raises():

View file

@ -7,8 +7,10 @@ import pytest
from mcp.types import AnyUrl, TextContent
from fastmcp import FastMCP
from fastmcp.prompts.prompt import FunctionPrompt, Prompt
from fastmcp.resources.resource import FunctionResource, Resource
from fastmcp.prompts.function_prompt import FunctionPrompt
from fastmcp.prompts.prompt import Prompt
from fastmcp.resources.function_resource import FunctionResource
from fastmcp.resources.resource import Resource
from fastmcp.resources.template import FunctionResourceTemplate, ResourceTemplate
from fastmcp.server.providers import Provider
from fastmcp.tools.tool import Tool, ToolResult