Jeremiah Lowin commited on
Commit
3257d51
·
unverified ·
1 Parent(s): 0907e77

Add meta support to tool transformation utilities (#1295)

Browse files
docs/patterns/tool-transformation.mdx CHANGED
@@ -92,6 +92,7 @@ The `Tool.from_tool()` class method is the primary way to create a transformed t
92
  - `tags`: An optional set of tags for the new tool.
93
  - `annotations`: An optional set of `ToolAnnotations` for the new tool.
94
  - `serializer`: An optional function that will be called to serialize the result of the new tool.
 
95
 
96
  The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method.
97
 
@@ -255,6 +256,50 @@ transform_args = {
255
  `default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client.
256
  </Warning>
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  ### Required Values
259
 
260
  In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required.
@@ -456,6 +501,10 @@ directly in the MCPConfig json file.
456
  "weather_get_forecast": {
457
  "name": "miami_weather",
458
  "description": "Get the weather for Miami",
 
 
 
 
459
  "arguments": {
460
  "city": {
461
  "name": "city",
 
92
  - `tags`: An optional set of tags for the new tool.
93
  - `annotations`: An optional set of `ToolAnnotations` for the new tool.
94
  - `serializer`: An optional function that will be called to serialize the result of the new tool.
95
+ - `meta`: Control meta information for the tool. Use `None` to remove meta, any dict to set meta, or leave unset to inherit from parent.
96
 
97
  The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method.
98
 
 
256
  `default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client.
257
  </Warning>
258
 
259
+ ### Meta Information
260
+
261
+ <VersionBadge version="2.11.0" />
262
+
263
+ You can control meta information on transformed tools using the `meta` parameter. Meta information is additional data about the tool that doesn't affect its functionality but can be used by clients for categorization, routing, or other purposes.
264
+
265
+ ```python {15-17}
266
+ from fastmcp import FastMCP
267
+ from fastmcp.tools import Tool
268
+
269
+ mcp = FastMCP()
270
+
271
+ @mcp.tool
272
+ def analyze_data(data: str) -> dict:
273
+ """Analyzes the provided data."""
274
+ return {"result": f"Analysis of {data}"}
275
+
276
+ # Add custom meta information
277
+ enhanced_tool = Tool.from_tool(
278
+ analyze_data,
279
+ name="enhanced_analyzer",
280
+ meta={
281
+ "category": "analytics",
282
+ "priority": "high",
283
+ "requires_auth": True
284
+ }
285
+ )
286
+
287
+ mcp.add_tool(enhanced_tool)
288
+ ```
289
+
290
+ You can also remove meta information entirely:
291
+
292
+ ```python {6}
293
+ # Remove meta information from parent tool
294
+ simplified_tool = Tool.from_tool(
295
+ analyze_data,
296
+ name="simple_analyzer",
297
+ meta=None # Removes any meta information
298
+ )
299
+ ```
300
+
301
+ If you don't specify the `meta` parameter, the transformed tool inherits the parent tool's meta information.
302
+
303
  ### Required Values
304
 
305
  In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required.
 
501
  "weather_get_forecast": {
502
  "name": "miami_weather",
503
  "description": "Get the weather for Miami",
504
+ "meta": {
505
+ "category": "weather",
506
+ "location": "miami"
507
+ },
508
  "arguments": {
509
  "city": {
510
  "name": "city",
src/fastmcp/tools/tool.py CHANGED
@@ -208,6 +208,7 @@ class Tool(FastMCPComponent):
208
  annotations: ToolAnnotations | None = None,
209
  output_schema: dict[str, Any] | None | Literal[False] = None,
210
  serializer: Callable[[Any], str] | None = None,
 
211
  enabled: bool | None = None,
212
  ) -> TransformedTool:
213
  from fastmcp.tools.tool_transform import TransformedTool
@@ -223,6 +224,7 @@ class Tool(FastMCPComponent):
223
  annotations=annotations,
224
  output_schema=output_schema,
225
  serializer=serializer,
 
226
  enabled=enabled,
227
  )
228
 
 
208
  annotations: ToolAnnotations | None = None,
209
  output_schema: dict[str, Any] | None | Literal[False] = None,
210
  serializer: Callable[[Any], str] | None = None,
211
+ meta: dict[str, Any] | None | NotSetT = NotSet,
212
  enabled: bool | None = None,
213
  ) -> TransformedTool:
214
  from fastmcp.tools.tool_transform import TransformedTool
 
224
  annotations=annotations,
225
  output_schema=output_schema,
226
  serializer=serializer,
227
+ meta=meta,
228
  enabled=enabled,
229
  )
230
 
src/fastmcp/tools/tool_transform.py CHANGED
@@ -366,6 +366,7 @@ class TransformedTool(Tool):
366
  annotations: ToolAnnotations | None = None,
367
  output_schema: dict[str, Any] | None | Literal[False] = None,
368
  serializer: Callable[[Any], str] | None = None,
 
369
  enabled: bool | None = None,
370
  ) -> TransformedTool:
371
  """Create a transformed tool from a parent tool.
@@ -390,6 +391,10 @@ class TransformedTool(Tool):
390
  - dict: Use custom output schema
391
  - False: Disable output schema and structured outputs
392
  serializer: New serializer. Defaults to parent's serializer.
 
 
 
 
393
 
394
  Returns:
395
  TransformedTool with the specified transformations.
@@ -546,6 +551,7 @@ class TransformedTool(Tool):
546
  description if not isinstance(description, NotSetT) else tool.description
547
  )
548
  final_title = title if not isinstance(title, NotSetT) else tool.title
 
549
 
550
  transformed_tool = cls(
551
  fn=final_fn,
@@ -559,6 +565,7 @@ class TransformedTool(Tool):
559
  tags=tags or tool.tags,
560
  annotations=annotations or tool.annotations,
561
  serializer=serializer or tool.serializer,
 
562
  transform_args=transform_args,
563
  enabled=enabled if enabled is not None else True,
564
  )
@@ -851,6 +858,10 @@ class ToolTransformConfig(FastMCPBaseModel):
851
  default_factory=set,
852
  description="The new tags for the tool.",
853
  )
 
 
 
 
854
 
855
  enabled: bool = Field(
856
  default=True,
 
366
  annotations: ToolAnnotations | None = None,
367
  output_schema: dict[str, Any] | None | Literal[False] = None,
368
  serializer: Callable[[Any], str] | None = None,
369
+ meta: dict[str, Any] | None | NotSetT = NotSet,
370
  enabled: bool | None = None,
371
  ) -> TransformedTool:
372
  """Create a transformed tool from a parent tool.
 
391
  - dict: Use custom output schema
392
  - False: Disable output schema and structured outputs
393
  serializer: New serializer. Defaults to parent's serializer.
394
+ meta: Control meta information:
395
+ - NotSet (default): Inherit from parent tool
396
+ - dict: Use custom meta information
397
+ - None: Remove meta information
398
 
399
  Returns:
400
  TransformedTool with the specified transformations.
 
551
  description if not isinstance(description, NotSetT) else tool.description
552
  )
553
  final_title = title if not isinstance(title, NotSetT) else tool.title
554
+ final_meta = meta if not isinstance(meta, NotSetT) else tool.meta
555
 
556
  transformed_tool = cls(
557
  fn=final_fn,
 
565
  tags=tags or tool.tags,
566
  annotations=annotations or tool.annotations,
567
  serializer=serializer or tool.serializer,
568
+ meta=final_meta,
569
  transform_args=transform_args,
570
  enabled=enabled if enabled is not None else True,
571
  )
 
858
  default_factory=set,
859
  description="The new tags for the tool.",
860
  )
861
+ meta: dict[str, Any] | None = Field(
862
+ default=None,
863
+ description="The new meta information for the tool.",
864
+ )
865
 
866
  enabled: bool = Field(
867
  default=True,
tests/tools/test_tool_transform.py CHANGED
@@ -13,7 +13,11 @@ from fastmcp.client.client import Client
13
  from fastmcp.exceptions import ToolError
14
  from fastmcp.tools import Tool, forward, forward_raw
15
  from fastmcp.tools.tool import FunctionTool, ToolResult
16
- from fastmcp.tools.tool_transform import ArgTransform, TransformedTool
 
 
 
 
17
 
18
 
19
  def get_property(tool: Tool, name: str) -> dict[str, Any]:
@@ -1428,3 +1432,65 @@ def test_transform_adds_description_to_none(sample_tool_no_title):
1428
  """Test that transformed tools can add description when parent has None."""
1429
  transformed = Tool.from_tool(sample_tool_no_title, description="Added description")
1430
  assert transformed.description == "Added description"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  from fastmcp.exceptions import ToolError
14
  from fastmcp.tools import Tool, forward, forward_raw
15
  from fastmcp.tools.tool import FunctionTool, ToolResult
16
+ from fastmcp.tools.tool_transform import (
17
+ ArgTransform,
18
+ ToolTransformConfig,
19
+ TransformedTool,
20
+ )
21
 
22
 
23
  def get_property(tool: Tool, name: str) -> dict[str, Any]:
 
1432
  """Test that transformed tools can add description when parent has None."""
1433
  transformed = Tool.from_tool(sample_tool_no_title, description="Added description")
1434
  assert transformed.description == "Added description"
1435
+
1436
+
1437
+ # Meta transformation tests
1438
+ def test_transform_inherits_meta(sample_tool):
1439
+ """Test that transformed tools inherit meta when none specified."""
1440
+ sample_tool.meta = {"original": True, "version": "1.0"}
1441
+ transformed = Tool.from_tool(sample_tool)
1442
+ assert transformed.meta == {"original": True, "version": "1.0"}
1443
+
1444
+
1445
+ def test_transform_overrides_meta(sample_tool):
1446
+ """Test that transformed tools can override meta."""
1447
+ sample_tool.meta = {"original": True, "version": "1.0"}
1448
+ transformed = Tool.from_tool(sample_tool, meta={"custom": True, "priority": "high"})
1449
+ assert transformed.meta == {"custom": True, "priority": "high"}
1450
+
1451
+
1452
+ def test_transform_sets_meta_to_none(sample_tool):
1453
+ """Test that transformed tools can explicitly set meta to None."""
1454
+ sample_tool.meta = {"original": True, "version": "1.0"}
1455
+ transformed = Tool.from_tool(sample_tool, meta=None)
1456
+ assert transformed.meta is None
1457
+
1458
+
1459
+ def test_transform_inherits_none_meta(sample_tool_no_title):
1460
+ """Test that transformed tools inherit None meta."""
1461
+ sample_tool_no_title.meta = None
1462
+ transformed = Tool.from_tool(sample_tool_no_title)
1463
+ assert transformed.meta is None
1464
+
1465
+
1466
+ def test_transform_adds_meta_to_none(sample_tool_no_title):
1467
+ """Test that transformed tools can add meta when parent has None."""
1468
+ sample_tool_no_title.meta = None
1469
+ transformed = Tool.from_tool(sample_tool_no_title, meta={"added": True})
1470
+ assert transformed.meta == {"added": True}
1471
+
1472
+
1473
+ def test_tool_transform_config_inherits_meta(sample_tool):
1474
+ """Test that ToolTransformConfig inherits meta when unset."""
1475
+ sample_tool.meta = {"original": True, "version": "1.0"}
1476
+ config = ToolTransformConfig(name="config_tool")
1477
+ transformed = config.apply(sample_tool)
1478
+ assert transformed.meta == {"original": True, "version": "1.0"}
1479
+
1480
+
1481
+ def test_tool_transform_config_overrides_meta(sample_tool):
1482
+ """Test that ToolTransformConfig can override meta."""
1483
+ sample_tool.meta = {"original": True, "version": "1.0"}
1484
+ config = ToolTransformConfig(
1485
+ name="config_tool", meta={"config": True, "priority": "high"}
1486
+ )
1487
+ transformed = config.apply(sample_tool)
1488
+ assert transformed.meta == {"config": True, "priority": "high"}
1489
+
1490
+
1491
+ def test_tool_transform_config_removes_meta(sample_tool):
1492
+ """Test that ToolTransformConfig can remove meta with None."""
1493
+ sample_tool.meta = {"original": True, "version": "1.0"}
1494
+ config = ToolTransformConfig(name="config_tool", meta=None)
1495
+ transformed = config.apply(sample_tool)
1496
+ assert transformed.meta is None