Ensure methods work/are documented

This commit is contained in:
Jeremiah Lowin 2025-06-05 10:59:32 -04:00
commit f495de6f3a
6 changed files with 202 additions and 54 deletions

View file

@ -171,6 +171,9 @@ class FunctionPrompt(Prompt):
# if the fn is a callable class, we need to get the __call__ method from here out
if not inspect.isroutine(fn):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
fn = fn.__func__
type_adapter = get_cached_typeadapter(fn)
parameters = type_adapter.json_schema()

View file

@ -225,8 +225,12 @@ class FunctionResourceTemplate(ResourceTemplate):
description = description or fn.__doc__
# if the fn is a callable class, we need to get the __call__ method from here out
if not inspect.isroutine(fn):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
fn = fn.__func__
type_adapter = get_cached_typeadapter(fn)
parameters = type_adapter.json_schema()

View file

@ -589,8 +589,20 @@ class FastMCP(Generic[LifespanResultT]):
if isinstance(annotations, dict):
annotations = ToolAnnotations(**annotations)
if isinstance(name_or_fn, classmethod):
raise ValueError(
inspect.cleandoc(
"""
To decorate a classmethod, first define the method and then call
tool() directly on the method instead of using it as a
decorator. See https://gofastmcp.com/patterns/decorating-methods
for examples and more information.
"""
)
)
# Determine the actual name and function based on the calling pattern
if callable(name_or_fn):
if inspect.isroutine(name_or_fn):
# Case 1: @tool (without parens) - function passed directly
# Case 2: direct call like tool(fn, name="something")
fn = name_or_fn
@ -747,7 +759,7 @@ class FastMCP(Generic[LifespanResultT]):
return f"Weather for {city}: {data}"
"""
# Check if user passed function directly instead of calling decorator
if callable(uri):
if inspect.isroutine(uri):
raise TypeError(
"The @resource decorator was used incorrectly. "
"Did you forget to call it? Use @resource('uri') instead of @resource"
@ -756,6 +768,18 @@ class FastMCP(Generic[LifespanResultT]):
def decorator(fn: AnyFunction) -> Resource | ResourceTemplate:
from fastmcp.server.context import Context
if isinstance(fn, classmethod):
raise ValueError(
inspect.cleandoc(
"""
To decorate a classmethod, first define the method and then call
resource() directly on the method instead of using it as a
decorator. See https://gofastmcp.com/patterns/decorating-methods
for examples and more information.
"""
)
)
# Check if this should be a template
has_uri_params = "{" in uri and "}" in uri
# check if the function has any parameters (other than injected context)
@ -896,8 +920,21 @@ class FastMCP(Generic[LifespanResultT]):
# Direct function call
server.prompt(my_function, name="custom_name")
"""
if isinstance(name_or_fn, classmethod):
raise ValueError(
inspect.cleandoc(
"""
To decorate a classmethod, first define the method and then call
prompt() directly on the method instead of using it as a
decorator. See https://gofastmcp.com/patterns/decorating-methods
for examples and more information.
"""
)
)
# Determine the actual name and function based on the calling pattern
if callable(name_or_fn):
if inspect.isroutine(name_or_fn):
# Case 1: @prompt (without parens) - function passed directly as decorator
# Case 2: direct call like prompt(fn, name="something")
fn = name_or_fn

View file

@ -146,6 +146,9 @@ class FunctionTool(Tool):
# if the fn is a callable class, we need to get the __call__ method from here out
if not inspect.isroutine(fn):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
fn = fn.__func__
type_adapter = get_cached_typeadapter(fn)
schema = type_adapter.json_schema()