Add meta support to tool transformation utilities (#1295)

This commit is contained in:
Jeremiah Lowin 2025-07-29 14:34:07 -07:00 committed by GitHub
commit 3e53e62f24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 129 additions and 1 deletions

View file

@ -92,6 +92,7 @@ The `Tool.from_tool()` class method is the primary way to create a transformed t
- `tags`: An optional set of tags for the new tool.
- `annotations`: An optional set of `ToolAnnotations` for the new tool.
- `serializer`: An optional function that will be called to serialize the result of the new tool.
- `meta`: Control meta information for the tool. Use `None` to remove meta, any dict to set meta, or leave unset to inherit from parent.
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.
@ -255,6 +256,50 @@ transform_args = {
`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.
</Warning>
### Meta Information
<VersionBadge version="2.11.0" />
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.
```python {15-17}
from fastmcp import FastMCP
from fastmcp.tools import Tool
mcp = FastMCP()
@mcp.tool
def analyze_data(data: str) -> dict:
"""Analyzes the provided data."""
return {"result": f"Analysis of {data}"}
# Add custom meta information
enhanced_tool = Tool.from_tool(
analyze_data,
name="enhanced_analyzer",
meta={
"category": "analytics",
"priority": "high",
"requires_auth": True
}
)
mcp.add_tool(enhanced_tool)
```
You can also remove meta information entirely:
```python {6}
# Remove meta information from parent tool
simplified_tool = Tool.from_tool(
analyze_data,
name="simple_analyzer",
meta=None # Removes any meta information
)
```
If you don't specify the `meta` parameter, the transformed tool inherits the parent tool's meta information.
### Required Values
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.
"weather_get_forecast": {
"name": "miami_weather",
"description": "Get the weather for Miami",
"meta": {
"category": "weather",
"location": "miami"
},
"arguments": {
"city": {
"name": "city",

View file

@ -208,6 +208,7 @@ class Tool(FastMCPComponent):
annotations: ToolAnnotations | None = None,
output_schema: dict[str, Any] | None | Literal[False] = None,
serializer: Callable[[Any], str] | None = None,
meta: dict[str, Any] | None | NotSetT = NotSet,
enabled: bool | None = None,
) -> TransformedTool:
from fastmcp.tools.tool_transform import TransformedTool
@ -223,6 +224,7 @@ class Tool(FastMCPComponent):
annotations=annotations,
output_schema=output_schema,
serializer=serializer,
meta=meta,
enabled=enabled,
)

View file

@ -366,6 +366,7 @@ class TransformedTool(Tool):
annotations: ToolAnnotations | None = None,
output_schema: dict[str, Any] | None | Literal[False] = None,
serializer: Callable[[Any], str] | None = None,
meta: dict[str, Any] | None | NotSetT = NotSet,
enabled: bool | None = None,
) -> TransformedTool:
"""Create a transformed tool from a parent tool.
@ -390,6 +391,10 @@ class TransformedTool(Tool):
- dict: Use custom output schema
- False: Disable output schema and structured outputs
serializer: New serializer. Defaults to parent's serializer.
meta: Control meta information:
- NotSet (default): Inherit from parent tool
- dict: Use custom meta information
- None: Remove meta information
Returns:
TransformedTool with the specified transformations.
@ -546,6 +551,7 @@ class TransformedTool(Tool):
description if not isinstance(description, NotSetT) else tool.description
)
final_title = title if not isinstance(title, NotSetT) else tool.title
final_meta = meta if not isinstance(meta, NotSetT) else tool.meta
transformed_tool = cls(
fn=final_fn,
@ -559,6 +565,7 @@ class TransformedTool(Tool):
tags=tags or tool.tags,
annotations=annotations or tool.annotations,
serializer=serializer or tool.serializer,
meta=final_meta,
transform_args=transform_args,
enabled=enabled if enabled is not None else True,
)
@ -851,6 +858,10 @@ class ToolTransformConfig(FastMCPBaseModel):
default_factory=set,
description="The new tags for the tool.",
)
meta: dict[str, Any] | None = Field(
default=None,
description="The new meta information for the tool.",
)
enabled: bool = Field(
default=True,

View file

@ -13,7 +13,11 @@ from fastmcp.client.client import Client
from fastmcp.exceptions import ToolError
from fastmcp.tools import Tool, forward, forward_raw
from fastmcp.tools.tool import FunctionTool, ToolResult
from fastmcp.tools.tool_transform import ArgTransform, TransformedTool
from fastmcp.tools.tool_transform import (
ArgTransform,
ToolTransformConfig,
TransformedTool,
)
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):
"""Test that transformed tools can add description when parent has None."""
transformed = Tool.from_tool(sample_tool_no_title, description="Added description")
assert transformed.description == "Added description"
# Meta transformation tests
def test_transform_inherits_meta(sample_tool):
"""Test that transformed tools inherit meta when none specified."""
sample_tool.meta = {"original": True, "version": "1.0"}
transformed = Tool.from_tool(sample_tool)
assert transformed.meta == {"original": True, "version": "1.0"}
def test_transform_overrides_meta(sample_tool):
"""Test that transformed tools can override meta."""
sample_tool.meta = {"original": True, "version": "1.0"}
transformed = Tool.from_tool(sample_tool, meta={"custom": True, "priority": "high"})
assert transformed.meta == {"custom": True, "priority": "high"}
def test_transform_sets_meta_to_none(sample_tool):
"""Test that transformed tools can explicitly set meta to None."""
sample_tool.meta = {"original": True, "version": "1.0"}
transformed = Tool.from_tool(sample_tool, meta=None)
assert transformed.meta is None
def test_transform_inherits_none_meta(sample_tool_no_title):
"""Test that transformed tools inherit None meta."""
sample_tool_no_title.meta = None
transformed = Tool.from_tool(sample_tool_no_title)
assert transformed.meta is None
def test_transform_adds_meta_to_none(sample_tool_no_title):
"""Test that transformed tools can add meta when parent has None."""
sample_tool_no_title.meta = None
transformed = Tool.from_tool(sample_tool_no_title, meta={"added": True})
assert transformed.meta == {"added": True}
def test_tool_transform_config_inherits_meta(sample_tool):
"""Test that ToolTransformConfig inherits meta when unset."""
sample_tool.meta = {"original": True, "version": "1.0"}
config = ToolTransformConfig(name="config_tool")
transformed = config.apply(sample_tool)
assert transformed.meta == {"original": True, "version": "1.0"}
def test_tool_transform_config_overrides_meta(sample_tool):
"""Test that ToolTransformConfig can override meta."""
sample_tool.meta = {"original": True, "version": "1.0"}
config = ToolTransformConfig(
name="config_tool", meta={"config": True, "priority": "high"}
)
transformed = config.apply(sample_tool)
assert transformed.meta == {"config": True, "priority": "high"}
def test_tool_transform_config_removes_meta(sample_tool):
"""Test that ToolTransformConfig can remove meta with None."""
sample_tool.meta = {"original": True, "version": "1.0"}
config = ToolTransformConfig(name="config_tool", meta=None)
transformed = config.apply(sample_tool)
assert transformed.meta is None