@@ -41,10 +41,9 @@ Base class for representing a tool to a generator.
4141### catch
4242
4343``` python
44- catch: bool | set[type[Exception ]] = {
45- JSONDecodeError,
46- ValidationError,
47- }
44+ catch: bool | set[type[Exception ]] = set (
45+ DEFAULT_CATCH_EXCEPTIONS
46+ )
4847```
4948
5049Whether to catch exceptions and return them as messages.
@@ -207,6 +206,8 @@ async def handle_tool_call( # noqa: PLR0912
207206 kwargs = json.loads(tool_call.function.arguments)
208207 if self ._type_adapter is not None :
209208 kwargs = self ._type_adapter.validate_python(kwargs)
209+ kwargs = kwargs or {}
210+
210211 dn.log_inputs(** kwargs)
211212
212213 # Call the function
@@ -367,7 +368,51 @@ def with_(
367368ToolMethod
368369----------
369370
370- A Tool wrapping a class method.
371+ ``` python
372+ ToolMethod(
373+ fget: Callable[... , Any],
374+ name: str ,
375+ description: str ,
376+ parameters_schema: dict[str , Any],
377+ catch: bool | Iterable[type[Exception ]] | None ,
378+ truncate: int | None ,
379+ signature: Signature,
380+ type_adapter: TypeAdapter[Any],
381+ )
382+ ```
383+
384+ A descriptor that acts as a factory for creating bound Tool instances.
385+
386+ It inherits from ` property ` to be ignored by pydantic's ` ModelMetaclass `
387+ during field inspection. This prevents validation errors which would
388+ otherwise treat the descriptor as a field and stop tool\_ method decorators
389+ from being applied in BaseModel classes.
390+
391+ <Accordion title = " Source code in rigging/tools/base.py" icon = " code" >
392+ ``` python
393+ def __init__ (
394+ self ,
395+ fget : t.Callable[... , t.Any],
396+ name : str ,
397+ description : str ,
398+ parameters_schema : dict[str , t.Any],
399+ catch : bool | t.Iterable[type[Exception ]] | None ,
400+ truncate : int | None ,
401+ signature : inspect.Signature,
402+ type_adapter : TypeAdapter[t.Any],
403+ ):
404+ super ().__init__ (fget)
405+ self .tool_name = name
406+ self .tool_description = description
407+ self .tool_parameters_schema = parameters_schema
408+ self .tool_catch = catch
409+ self .tool_truncate = truncate
410+ self ._tool_signature = signature
411+ self ._tool_type_adapter = type_adapter
412+ ```
413+
414+
415+ </Accordion >
371416
372417tool
373418----
@@ -621,41 +666,38 @@ def tool_method(
621666 ~~~
622667 """
623668
624- def make_tool (func : t.Callable[... , t.Any]) -> ToolMethod[P, R]:
625- # TODO : Improve consistency of detection here before enabling this warning
626- # if not _is_unbound_method(func):
627- # warnings.warn(
628- # "Passing a regular function to @tool_method improperly handles the 'self' argument, use @tool instead.",
629- # SyntaxWarning,
630- # stacklevel=3,
631- # )
669+ def make_tool (f : t.Callable[t.Concatenate[t.Any, P], R]) -> ToolMethod[P, R]:
670+ # This logic is specialized from `Tool.from_callable` to correctly
671+ # handle the `self` parameter in method signatures.
632672
633- # Strip the `self` argument from the function signature so
634- # our schema generation doesn't include it under the hood.
673+ signature = inspect.signature(f)
674+ params_without_self = [p for p_name, p in signature.parameters.items() if p_name != " self" ]
675+ schema_signature = signature.replace(parameters = params_without_self)
635676
636- @functools.wraps (func )
637- def wrapper ( self : t.Any, * args : P.args, ** kwargs : P.kwargs ) -> R :
638- return func( self , * args, ** kwargs) # type: ignore [ no - any - return ]
677+ @functools.wraps (f )
678+ def empty_func ( * _ : t.Any, ** kwargs : t.Any ) -> t.Any :
679+ return kwargs
639680
640- wrapper.__signature__ = inspect.signature(func).replace( # type: ignore [ attr -defined ]
641- parameters = tuple (
642- param
643- for param in inspect.signature(func).parameters.values()
644- if param.name != " self"
645- ),
646- )
681+ empty_func.__signature__ = schema_signature # type: ignore [ attr -defined ]
682+ type_adapter: TypeAdapter[t.Any] = TypeAdapter(empty_func)
683+ schema = deref_json(type_adapter.json_schema(), is_json_schema = True )
647684
648- return ToolMethod.from_callable(
649- wrapper, # type: ignore [ arg -type ]
650- name = name,
651- description = description,
685+ tool_name = name or f.__name__
686+ tool_description = inspect.cleandoc(description or f.__doc__ or " " )
687+
688+ return ToolMethod(
689+ fget = f,
690+ name = tool_name,
691+ description = tool_description,
692+ parameters_schema = schema,
652693 catch = catch,
653694 truncate = truncate,
695+ signature = schema_signature,
696+ type_adapter = type_adapter,
654697 )
655698
656699 if func is not None :
657700 return make_tool(func)
658-
659701 return make_tool
660702```
661703
@@ -677,7 +719,7 @@ A client for communicating with MCP servers.
677719
678720<Accordion title = " Source code in rigging/tools/mcp.py" icon = " code" >
679721``` python
680- def __init__ (self , transport : Transport, connection : StdioConnection | SSEConnection) -> None :
722+ def __init__ (self , transport : Transport, connection : " StdioConnection | SSEConnection" ) -> None :
681723 self .transport = transport
682724 self .connection = connection
683725 self .tools = []
@@ -793,6 +835,9 @@ def as_mcp(
793835 )
794836 ~~~
795837 """
838+ from mcp.server.fastmcp import FastMCP
839+ from mcp.server.fastmcp.tools import Tool as FastMCPTool
840+
796841 rigging_tools: list[Tool[... , t.Any]] = []
797842 for tool in flatten_list(list (tools)):
798843 interior_tools = [
0 commit comments