fastmcp 2.9.0__py3-none-any.whl → 2.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastmcp/server/server.py CHANGED
@@ -23,7 +23,6 @@ import mcp.types
23
23
  import uvicorn
24
24
  from mcp.server.lowlevel.helper_types import ReadResourceContents
25
25
  from mcp.server.lowlevel.server import LifespanResultT, NotificationOptions
26
- from mcp.server.lowlevel.server import Server as MCPServer
27
26
  from mcp.server.stdio import stdio_server
28
27
  from mcp.types import (
29
28
  AnyFunction,
@@ -54,6 +53,7 @@ from fastmcp.server.http import (
54
53
  create_sse_app,
55
54
  create_streamable_http_app,
56
55
  )
56
+ from fastmcp.server.low_level import LowLevelServer
57
57
  from fastmcp.server.middleware import Middleware, MiddlewareContext
58
58
  from fastmcp.settings import Settings
59
59
  from fastmcp.tools import ToolManager
@@ -99,10 +99,12 @@ def _lifespan_wrapper(
99
99
  [FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
100
100
  ],
101
101
  ) -> Callable[
102
- [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
102
+ [LowLevelServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
103
103
  ]:
104
104
  @asynccontextmanager
105
- async def wrap(s: MCPServer[LifespanResultT]) -> AsyncIterator[LifespanResultT]:
105
+ async def wrap(
106
+ s: LowLevelServer[LifespanResultT],
107
+ ) -> AsyncIterator[LifespanResultT]:
106
108
  async with AsyncExitStack() as stack:
107
109
  context = await stack.enter_async_context(lifespan(app))
108
110
  yield context
@@ -179,7 +181,7 @@ class FastMCP(Generic[LifespanResultT]):
179
181
  lifespan = default_lifespan
180
182
  else:
181
183
  self._has_lifespan = True
182
- self._mcp_server = MCPServer[LifespanResultT](
184
+ self._mcp_server = LowLevelServer[LifespanResultT](
183
185
  name=name or "FastMCP",
184
186
  version=version,
185
187
  instructions=instructions,
@@ -363,6 +365,7 @@ class FastMCP(Generic[LifespanResultT]):
363
365
  return await self._resource_manager.get_resource_templates()
364
366
 
365
367
  async def get_resource_template(self, key: str) -> ResourceTemplate:
368
+ """Get a registered resource template by key."""
366
369
  templates = await self.get_resource_templates()
367
370
  if key not in templates:
368
371
  raise NotFoundError(f"Unknown resource template: {key}")
@@ -403,9 +406,12 @@ class FastMCP(Generic[LifespanResultT]):
403
406
  include_in_schema: Whether to include in OpenAPI schema, defaults to True
404
407
 
405
408
  Example:
409
+ Register a custom HTTP route for a health check endpoint:
410
+ ```python
406
411
  @server.custom_route("/health", methods=["GET"])
407
412
  async def health_check(request: Request) -> Response:
408
413
  return JSONResponse({"status": "ok"})
414
+ ```
409
415
  """
410
416
 
411
417
  def decorator(
@@ -427,7 +433,7 @@ class FastMCP(Generic[LifespanResultT]):
427
433
  async def _mcp_list_tools(self) -> list[MCPTool]:
428
434
  logger.debug("Handler called: list_tools")
429
435
 
430
- with fastmcp.server.context.Context(fastmcp=self):
436
+ async with fastmcp.server.context.Context(fastmcp=self):
431
437
  tools = await self._list_tools()
432
438
  return [tool.to_mcp_tool(name=tool.key) for tool in tools]
433
439
 
@@ -450,7 +456,7 @@ class FastMCP(Generic[LifespanResultT]):
450
456
 
451
457
  return mcp_tools
452
458
 
453
- with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
459
+ async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
454
460
  # Create the middleware context.
455
461
  mw_context = MiddlewareContext(
456
462
  message=mcp.types.ListToolsRequest(method="tools/list"),
@@ -466,7 +472,7 @@ class FastMCP(Generic[LifespanResultT]):
466
472
  async def _mcp_list_resources(self) -> list[MCPResource]:
467
473
  logger.debug("Handler called: list_resources")
468
474
 
469
- with fastmcp.server.context.Context(fastmcp=self):
475
+ async with fastmcp.server.context.Context(fastmcp=self):
470
476
  resources = await self._list_resources()
471
477
  return [
472
478
  resource.to_mcp_resource(uri=resource.key) for resource in resources
@@ -491,7 +497,7 @@ class FastMCP(Generic[LifespanResultT]):
491
497
 
492
498
  return mcp_resources
493
499
 
494
- with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
500
+ async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
495
501
  # Create the middleware context.
496
502
  mw_context = MiddlewareContext(
497
503
  message={}, # List resources doesn't have parameters
@@ -507,7 +513,7 @@ class FastMCP(Generic[LifespanResultT]):
507
513
  async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
508
514
  logger.debug("Handler called: list_resource_templates")
509
515
 
510
- with fastmcp.server.context.Context(fastmcp=self):
516
+ async with fastmcp.server.context.Context(fastmcp=self):
511
517
  templates = await self._list_resource_templates()
512
518
  return [
513
519
  template.to_mcp_template(uriTemplate=template.key)
@@ -533,7 +539,7 @@ class FastMCP(Generic[LifespanResultT]):
533
539
 
534
540
  return mcp_templates
535
541
 
536
- with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
542
+ async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
537
543
  # Create the middleware context.
538
544
  mw_context = MiddlewareContext(
539
545
  message={}, # List resource templates doesn't have parameters
@@ -549,7 +555,7 @@ class FastMCP(Generic[LifespanResultT]):
549
555
  async def _mcp_list_prompts(self) -> list[MCPPrompt]:
550
556
  logger.debug("Handler called: list_prompts")
551
557
 
552
- with fastmcp.server.context.Context(fastmcp=self):
558
+ async with fastmcp.server.context.Context(fastmcp=self):
553
559
  prompts = await self._list_prompts()
554
560
  return [prompt.to_mcp_prompt(name=prompt.key) for prompt in prompts]
555
561
 
@@ -572,7 +578,7 @@ class FastMCP(Generic[LifespanResultT]):
572
578
 
573
579
  return mcp_prompts
574
580
 
575
- with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
581
+ async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
576
582
  # Create the middleware context.
577
583
  mw_context = MiddlewareContext(
578
584
  message=mcp.types.ListPromptsRequest(method="prompts/list"),
@@ -602,7 +608,7 @@ class FastMCP(Generic[LifespanResultT]):
602
608
  """
603
609
  logger.debug("Handler called: call_tool %s with %s", key, arguments)
604
610
 
605
- with fastmcp.server.context.Context(fastmcp=self):
611
+ async with fastmcp.server.context.Context(fastmcp=self):
606
612
  try:
607
613
  return await self._call_tool(key, arguments)
608
614
  except DisabledError:
@@ -643,7 +649,7 @@ class FastMCP(Generic[LifespanResultT]):
643
649
  """
644
650
  logger.debug("Handler called: read_resource %s", uri)
645
651
 
646
- with fastmcp.server.context.Context(fastmcp=self):
652
+ async with fastmcp.server.context.Context(fastmcp=self):
647
653
  try:
648
654
  return await self._read_resource(uri)
649
655
  except DisabledError:
@@ -698,7 +704,7 @@ class FastMCP(Generic[LifespanResultT]):
698
704
  """
699
705
  logger.debug("Handler called: get_prompt %s with %s", name, arguments)
700
706
 
701
- with fastmcp.server.context.Context(fastmcp=self):
707
+ async with fastmcp.server.context.Context(fastmcp=self):
702
708
  try:
703
709
  return await self._get_prompt(name, arguments)
704
710
  except DisabledError:
@@ -747,6 +753,15 @@ class FastMCP(Generic[LifespanResultT]):
747
753
  self._tool_manager.add_tool(tool)
748
754
  self._cache.clear()
749
755
 
756
+ # Send notification if we're in a request context
757
+ try:
758
+ from fastmcp.server.dependencies import get_context
759
+
760
+ context = get_context()
761
+ context._queue_tool_list_changed() # type: ignore[private-use]
762
+ except RuntimeError:
763
+ pass # No context available
764
+
750
765
  def remove_tool(self, name: str) -> None:
751
766
  """Remove a tool from the server.
752
767
 
@@ -759,6 +774,15 @@ class FastMCP(Generic[LifespanResultT]):
759
774
  self._tool_manager.remove_tool(name)
760
775
  self._cache.clear()
761
776
 
777
+ # Send notification if we're in a request context
778
+ try:
779
+ from fastmcp.server.dependencies import get_context
780
+
781
+ context = get_context()
782
+ context._queue_tool_list_changed() # type: ignore[private-use]
783
+ except RuntimeError:
784
+ pass # No context available
785
+
762
786
  @overload
763
787
  def tool(
764
788
  self,
@@ -814,15 +838,18 @@ class FastMCP(Generic[LifespanResultT]):
814
838
  name: Optional name for the tool (keyword-only, alternative to name_or_fn)
815
839
  description: Optional description of what the tool does
816
840
  tags: Optional set of tags for categorizing the tool
817
- annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
841
+ annotations: Optional annotations about the tool's behavior
818
842
  exclude_args: Optional list of argument names to exclude from the tool schema
819
843
  enabled: Optional boolean to enable or disable the tool
820
844
 
821
- Example:
845
+ Examples:
846
+ Register a tool with a custom name:
847
+ ```python
822
848
  @server.tool
823
849
  def my_tool(x: int) -> str:
824
850
  return str(x)
825
851
 
852
+ # Register a tool with a custom name
826
853
  @server.tool
827
854
  def my_tool(x: int) -> str:
828
855
  return str(x)
@@ -837,6 +864,7 @@ class FastMCP(Generic[LifespanResultT]):
837
864
 
838
865
  # Direct function call
839
866
  server.tool(my_function, name="custom_name")
867
+ ```
840
868
  """
841
869
  if isinstance(annotations, dict):
842
870
  annotations = ToolAnnotations(**annotations)
@@ -911,6 +939,15 @@ class FastMCP(Generic[LifespanResultT]):
911
939
  self._resource_manager.add_resource(resource)
912
940
  self._cache.clear()
913
941
 
942
+ # Send notification if we're in a request context
943
+ try:
944
+ from fastmcp.server.dependencies import get_context
945
+
946
+ context = get_context()
947
+ context._queue_resource_list_changed() # type: ignore[private-use]
948
+ except RuntimeError:
949
+ pass # No context available
950
+
914
951
  def add_template(self, template: ResourceTemplate) -> None:
915
952
  """Add a resource template to the server.
916
953
 
@@ -919,6 +956,15 @@ class FastMCP(Generic[LifespanResultT]):
919
956
  """
920
957
  self._resource_manager.add_template(template)
921
958
 
959
+ # Send notification if we're in a request context
960
+ try:
961
+ from fastmcp.server.dependencies import get_context
962
+
963
+ context = get_context()
964
+ context._queue_resource_list_changed() # type: ignore[private-use]
965
+ except RuntimeError:
966
+ pass # No context available
967
+
922
968
  def add_resource_fn(
923
969
  self,
924
970
  fn: AnyFunction,
@@ -991,7 +1037,9 @@ class FastMCP(Generic[LifespanResultT]):
991
1037
  tags: Optional set of tags for categorizing the resource
992
1038
  enabled: Optional boolean to enable or disable the resource
993
1039
 
994
- Example:
1040
+ Examples:
1041
+ Register a resource with a custom name:
1042
+ ```python
995
1043
  @server.resource("resource://my-resource")
996
1044
  def get_data() -> str:
997
1045
  return "Hello, world!"
@@ -1014,6 +1062,7 @@ class FastMCP(Generic[LifespanResultT]):
1014
1062
  async def get_weather(city: str) -> str:
1015
1063
  data = await fetch_weather(city)
1016
1064
  return f"Weather for {city}: {data}"
1065
+ ```
1017
1066
  """
1018
1067
  # Check if user passed function directly instead of calling decorator
1019
1068
  if inspect.isroutine(uri):
@@ -1087,6 +1136,15 @@ class FastMCP(Generic[LifespanResultT]):
1087
1136
  self._prompt_manager.add_prompt(prompt)
1088
1137
  self._cache.clear()
1089
1138
 
1139
+ # Send notification if we're in a request context
1140
+ try:
1141
+ from fastmcp.server.dependencies import get_context
1142
+
1143
+ context = get_context()
1144
+ context._queue_prompt_list_changed() # type: ignore[private-use]
1145
+ except RuntimeError:
1146
+ pass # No context available
1147
+
1090
1148
  @overload
1091
1149
  def prompt(
1092
1150
  self,
@@ -1138,7 +1196,9 @@ class FastMCP(Generic[LifespanResultT]):
1138
1196
  tags: Optional set of tags for categorizing the prompt
1139
1197
  enabled: Optional boolean to enable or disable the prompt
1140
1198
 
1141
- Example:
1199
+ Examples:
1200
+
1201
+ ```python
1142
1202
  @server.prompt
1143
1203
  def analyze_table(table_name: str) -> list[Message]:
1144
1204
  schema = read_table_schema(table_name)
@@ -1182,6 +1242,7 @@ class FastMCP(Generic[LifespanResultT]):
1182
1242
 
1183
1243
  # Direct function call
1184
1244
  server.prompt(my_function, name="custom_name")
1245
+ ```
1185
1246
  """
1186
1247
 
1187
1248
  if isinstance(name_or_fn, classmethod):
@@ -1787,10 +1848,10 @@ class FastMCP(Generic[LifespanResultT]):
1787
1848
  ) -> FastMCPProxy:
1788
1849
  """Create a FastMCP proxy server for the given backend.
1789
1850
 
1790
- The ``backend`` argument can be either an existing :class:`~fastmcp.client.Client`
1791
- instance or any value accepted as the ``transport`` argument of
1792
- :class:`~fastmcp.client.Client`. This mirrors the convenience of the
1793
- ``Client`` constructor.
1851
+ The `backend` argument can be either an existing `fastmcp.client.Client`
1852
+ instance or any value accepted as the `transport` argument of
1853
+ `fastmcp.client.Client`. This mirrors the convenience of the
1854
+ `fastmcp.client.Client` constructor.
1794
1855
  """
1795
1856
  from fastmcp.client.client import Client
1796
1857
  from fastmcp.server.proxy import FastMCPProxy
@@ -1827,14 +1888,14 @@ class FastMCP(Generic[LifespanResultT]):
1827
1888
  Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not.
1828
1889
 
1829
1890
  Rules:
1830
- If the component's enabled property is False, always return False.
1831
- If both include_tags and exclude_tags are None, return True.
1832
- If exclude_tags is provided, check each exclude tag:
1891
+ - If the component's enabled property is False, always return False.
1892
+ - If both include_tags and exclude_tags are None, return True.
1893
+ - If exclude_tags is provided, check each exclude tag:
1833
1894
  - If the exclude tag is a string, it must be present in the input tags to exclude.
1834
- If include_tags is provided, check each include tag:
1895
+ - If include_tags is provided, check each include tag:
1835
1896
  - If the include tag is a string, it must be present in the input tags to include.
1836
- If include_tags is provided and none of the include tags match, return False.
1837
- If include_tags is not provided, return True.
1897
+ - If include_tags is provided and none of the include tags match, return False.
1898
+ - If include_tags is not provided, return True.
1838
1899
  """
1839
1900
  if not component.enabled:
1840
1901
  return False
@@ -1875,12 +1936,21 @@ def add_resource_prefix(
1875
1936
  The resource URI with the prefix added
1876
1937
 
1877
1938
  Examples:
1878
- >>> add_resource_prefix("resource://path/to/resource", "prefix")
1879
- "resource://prefix/path/to/resource" # with new style
1880
- >>> add_resource_prefix("resource://path/to/resource", "prefix")
1881
- "prefix+resource://path/to/resource" # with legacy style
1882
- >>> add_resource_prefix("resource:///absolute/path", "prefix")
1883
- "resource://prefix//absolute/path" # with new style
1939
+ With new style:
1940
+ ```python
1941
+ add_resource_prefix("resource://path/to/resource", "prefix")
1942
+ "resource://prefix/path/to/resource"
1943
+ ```
1944
+ With legacy style:
1945
+ ```python
1946
+ add_resource_prefix("resource://path/to/resource", "prefix")
1947
+ "prefix+resource://path/to/resource"
1948
+ ```
1949
+ With absolute path:
1950
+ ```python
1951
+ add_resource_prefix("resource:///absolute/path", "prefix")
1952
+ "resource://prefix//absolute/path"
1953
+ ```
1884
1954
 
1885
1955
  Raises:
1886
1956
  ValueError: If the URI doesn't match the expected protocol://path format
@@ -1926,12 +1996,21 @@ def remove_resource_prefix(
1926
1996
  The resource URI with the prefix removed
1927
1997
 
1928
1998
  Examples:
1929
- >>> remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
1930
- "resource://path/to/resource" # with new style
1931
- >>> remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
1932
- "resource://path/to/resource" # with legacy style
1933
- >>> remove_resource_prefix("resource://prefix//absolute/path", "prefix")
1934
- "resource:///absolute/path" # with new style
1999
+ With new style:
2000
+ ```python
2001
+ remove_resource_prefix("resource://prefix/path/to/resource", "prefix")
2002
+ "resource://path/to/resource"
2003
+ ```
2004
+ With legacy style:
2005
+ ```python
2006
+ remove_resource_prefix("prefix+resource://path/to/resource", "prefix")
2007
+ "resource://path/to/resource"
2008
+ ```
2009
+ With absolute path:
2010
+ ```python
2011
+ remove_resource_prefix("resource://prefix//absolute/path", "prefix")
2012
+ "resource:///absolute/path"
2013
+ ```
1935
2014
 
1936
2015
  Raises:
1937
2016
  ValueError: If the URI doesn't match the expected protocol://path format
@@ -1984,12 +2063,21 @@ def has_resource_prefix(
1984
2063
  True if the URI has the specified prefix, False otherwise
1985
2064
 
1986
2065
  Examples:
1987
- >>> has_resource_prefix("resource://prefix/path/to/resource", "prefix")
1988
- True # with new style
1989
- >>> has_resource_prefix("prefix+resource://path/to/resource", "prefix")
1990
- True # with legacy style
1991
- >>> has_resource_prefix("resource://other/path/to/resource", "prefix")
2066
+ With new style:
2067
+ ```python
2068
+ has_resource_prefix("resource://prefix/path/to/resource", "prefix")
2069
+ True
2070
+ ```
2071
+ With legacy style:
2072
+ ```python
2073
+ has_resource_prefix("prefix+resource://path/to/resource", "prefix")
2074
+ True
2075
+ ```
2076
+ With other path:
2077
+ ```python
2078
+ has_resource_prefix("resource://other/path/to/resource", "prefix")
1992
2079
  False
2080
+ ```
1993
2081
 
1994
2082
  Raises:
1995
2083
  ValueError: If the URI doesn't match the expected protocol://path format
fastmcp/tools/tool.py CHANGED
@@ -46,6 +46,22 @@ class Tool(FastMCPComponent):
46
46
  default=None, description="Optional custom serializer for tool results"
47
47
  )
48
48
 
49
+ def enable(self) -> None:
50
+ super().enable()
51
+ try:
52
+ context = get_context()
53
+ context._queue_tool_list_changed() # type: ignore[private-use]
54
+ except RuntimeError:
55
+ pass # No context available
56
+
57
+ def disable(self) -> None:
58
+ super().disable()
59
+ try:
60
+ context = get_context()
61
+ context._queue_tool_list_changed() # type: ignore[private-use]
62
+ except RuntimeError:
63
+ pass # No context available
64
+
49
65
  def to_mcp_tool(self, **overrides: Any) -> MCPTool:
50
66
  kwargs = {
51
67
  "name": self.name,
@@ -186,12 +186,12 @@ class ToolManager:
186
186
 
187
187
  # raise ToolErrors as-is
188
188
  except ToolError as e:
189
- logger.exception(f"Error calling tool {key!r}: {e}")
189
+ logger.exception(f"Error calling tool {key!r}")
190
190
  raise e
191
191
 
192
192
  # Handle other exceptions
193
193
  except Exception as e:
194
- logger.exception(f"Error calling tool {key!r}: {e}")
194
+ logger.exception(f"Error calling tool {key!r}")
195
195
  if self.mask_error_details:
196
196
  # Mask internal details
197
197
  raise ToolError(f"Error calling tool {key!r}") from e
@@ -100,35 +100,55 @@ class ArgTransform:
100
100
  examples: Examples for the argument. Use ... for no change.
101
101
 
102
102
  Examples:
103
- # Rename argument 'old_name' to 'new_name'
103
+ Rename argument 'old_name' to 'new_name'
104
+ ```python
104
105
  ArgTransform(name="new_name")
106
+ ```
105
107
 
106
- # Change description only
108
+ Change description only
109
+ ```python
107
110
  ArgTransform(description="Updated description")
111
+ ```
108
112
 
109
- # Add a default value (makes argument optional)
113
+ Add a default value (makes argument optional)
114
+ ```python
110
115
  ArgTransform(default=42)
116
+ ```
111
117
 
112
- # Add a default factory (makes argument optional)
118
+ Add a default factory (makes argument optional)
119
+ ```python
113
120
  ArgTransform(default_factory=lambda: time.time())
121
+ ```
114
122
 
115
- # Change the type
123
+ Change the type
124
+ ```python
116
125
  ArgTransform(type=str)
126
+ ```
117
127
 
118
- # Hide the argument entirely from clients
128
+ Hide the argument entirely from clients
129
+ ```python
119
130
  ArgTransform(hide=True)
131
+ ```
120
132
 
121
- # Hide argument but pass a constant value to parent
133
+ Hide argument but pass a constant value to parent
134
+ ```python
122
135
  ArgTransform(hide=True, default="constant_value")
136
+ ```
123
137
 
124
- # Hide argument but pass a factory-generated value to parent
138
+ Hide argument but pass a factory-generated value to parent
139
+ ```python
125
140
  ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex)
141
+ ```
126
142
 
127
- # Make an optional parameter required (removes any default)
143
+ Make an optional parameter required (removes any default)
144
+ ```python
128
145
  ArgTransform(required=True)
146
+ ```
129
147
 
130
- # Combine multiple transformations
148
+ Combine multiple transformations
149
+ ```python
131
150
  ArgTransform(name="new_name", description="New desc", default=None, type=int)
151
+ ```
132
152
  """
133
153
 
134
154
  name: str | EllipsisType = NotSet
@@ -279,9 +299,9 @@ class TransformedTool(Tool):
279
299
  name: New name for the tool. Defaults to parent tool's name.
280
300
  transform_args: Optional transformations for parent tool arguments.
281
301
  Only specified arguments are transformed, others pass through unchanged:
282
- - str: Simple rename
283
- - ArgTransform: Complex transformation (rename/description/default/drop)
284
- - None: Drop the argument
302
+ - Simple rename (str)
303
+ - Complex transformation (rename/description/default/drop) (ArgTransform)
304
+ - Drop the argument (None)
285
305
  description: New description. Defaults to parent's description.
286
306
  tags: New tags. Defaults to parent's tags.
287
307
  annotations: New annotations. Defaults to parent's annotations.
@@ -290,23 +310,29 @@ class TransformedTool(Tool):
290
310
  Returns:
291
311
  TransformedTool with the specified transformations.
292
312
 
293
- Examples:
313
+ Examples:
294
314
  # Transform specific arguments only
315
+ ```python
295
316
  Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged
317
+ ```
296
318
 
297
319
  # Custom function with partial transforms
320
+ ```python
298
321
  async def custom(x: int, y: int) -> str:
299
322
  result = await forward(x=x, y=y)
300
323
  return f"Custom: {result}"
301
324
 
302
325
  Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"})
326
+ ```
303
327
 
304
328
  # Using **kwargs (gets all args, transformed and untransformed)
329
+ ```python
305
330
  async def flexible(**kwargs) -> str:
306
331
  result = await forward(**kwargs)
307
332
  return f"Got: {kwargs}"
308
333
 
309
334
  Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
335
+ ```
310
336
  """
311
337
  transform_args = transform_args or {}
312
338
 
@@ -423,8 +449,8 @@ class TransformedTool(Tool):
423
449
 
424
450
  Returns:
425
451
  A tuple containing:
426
- - dict: The new JSON schema for the transformed tool
427
- - Callable: Async function that validates and forwards calls to the parent tool
452
+ - The new JSON schema for the transformed tool as a dictionary
453
+ - Async function that validates and forwards calls to the parent tool
428
454
  """
429
455
 
430
456
  # Build transformed schema and mapping