diff --git a/docs/python-sdk/fastmcp-server-server.mdx b/docs/python-sdk/fastmcp-server-server.mdx index 359b3dbd0..8e6cc2bf5 100644 --- a/docs/python-sdk/fastmcp-server-server.mdx +++ b/docs/python-sdk/fastmcp-server-server.mdx @@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers. ## Functions -### `add_resource_prefix` +### `add_resource_prefix` ```python add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str @@ -28,18 +28,27 @@ Add a prefix to a resource URI. **Examples:** ->>> add_resource_prefix("resource://path/to/resource", "prefix") -"resource://prefix/path/to/resource" # with new style ->>> add_resource_prefix("resource://path/to/resource", "prefix") -"prefix+resource://path/to/resource" # with legacy style ->>> add_resource_prefix("resource:///absolute/path", "prefix") -"resource://prefix//absolute/path" # with new style +With new style: +```python +add_resource_prefix("resource://path/to/resource", "prefix") +"resource://prefix/path/to/resource" +``` +With legacy style: +```python +add_resource_prefix("resource://path/to/resource", "prefix") +"prefix+resource://path/to/resource" +``` +With absolute path: +```python +add_resource_prefix("resource:///absolute/path", "prefix") +"resource://prefix//absolute/path" +``` **Raises:** - `ValueError`: If the URI doesn't match the expected protocol\://path format -### `remove_resource_prefix` +### `remove_resource_prefix` ```python remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str @@ -58,18 +67,27 @@ Returns: **Examples:** ->>> remove_resource_prefix("resource://prefix/path/to/resource", "prefix") -"resource://path/to/resource" # with new style ->>> remove_resource_prefix("prefix+resource://path/to/resource", "prefix") -"resource://path/to/resource" # with legacy style ->>> remove_resource_prefix("resource://prefix//absolute/path", "prefix") -"resource:///absolute/path" # with new style +With new style: +```python +remove_resource_prefix("resource://prefix/path/to/resource", "prefix") +"resource://path/to/resource" +``` +With legacy style: +```python +remove_resource_prefix("prefix+resource://path/to/resource", "prefix") +"resource://path/to/resource" +``` +With absolute path: +```python +remove_resource_prefix("resource://prefix//absolute/path", "prefix") +"resource:///absolute/path" +``` **Raises:** - `ValueError`: If the URI doesn't match the expected protocol\://path format -### `has_resource_prefix` +### `has_resource_prefix` ```python has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool @@ -87,12 +105,21 @@ Check if a resource URI has a specific prefix. **Examples:** ->>> has_resource_prefix("resource://prefix/path/to/resource", "prefix") -True # with new style ->>> has_resource_prefix("prefix+resource://path/to/resource", "prefix") -True # with legacy style ->>> has_resource_prefix("resource://other/path/to/resource", "prefix") +With new style: +```python +has_resource_prefix("resource://prefix/path/to/resource", "prefix") +True +``` +With legacy style: +```python +has_resource_prefix("prefix+resource://path/to/resource", "prefix") +True +``` +With other path: +```python +has_resource_prefix("resource://other/path/to/resource", "prefix") False +``` **Raises:** - `ValueError`: If the URI doesn't match the expected protocol\://path format @@ -140,7 +167,7 @@ Run the FastMCP server. Note this is a synchronous function. add_middleware(self, middleware: Middleware) -> None ``` -#### `custom_route` +#### `custom_route` ```python custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) @@ -161,7 +188,7 @@ Starlette's reverse URL lookup feature) - `include_in_schema`: Whether to include in OpenAPI schema, defaults to True -#### `add_tool` +#### `add_tool` ```python add_tool(self, tool: Tool) -> None @@ -176,7 +203,7 @@ with the Context type annotation. See the @tool decorator for examples. - `tool`: The Tool instance to register -#### `remove_tool` +#### `remove_tool` ```python remove_tool(self, name: str) -> None @@ -191,19 +218,19 @@ Remove a tool from the server. - `NotFoundError`: If the tool is not found -#### `tool` +#### `tool` ```python tool(self, name_or_fn: AnyFunction) -> FunctionTool ``` -#### `tool` +#### `tool` ```python tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool] ``` -#### `tool` +#### `tool` ```python tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool @@ -227,12 +254,37 @@ This decorator supports multiple calling patterns: - `name`: Optional name for the tool (keyword-only, alternative to name_or_fn) - `description`: Optional description of what the tool does - `tags`: Optional set of tags for categorizing the tool -- `annotations`: Optional annotations about the tool's behavior (e.g. {"is_async"\: True}) +- `annotations`: Optional annotations about the tool's behavior - `exclude_args`: Optional list of argument names to exclude from the tool schema - `enabled`: Optional boolean to enable or disable the tool +**Examples:** -#### `add_resource` +Register a tool with a custom name: +```python +@server.tool +def my_tool(x: int) -> str: + return str(x) + +# Register a tool with a custom name +@server.tool +def my_tool(x: int) -> str: + return str(x) + +@server.tool("custom_name") +def my_tool(x: int) -> str: + return str(x) + +@server.tool(name="custom_name") +def my_tool(x: int) -> str: + return str(x) + +# Direct function call +server.tool(my_function, name="custom_name") +``` + + +#### `add_resource` ```python add_resource(self, resource: Resource) -> None @@ -244,7 +296,7 @@ Add a resource to the server. - `resource`: A Resource instance to add -#### `add_template` +#### `add_template` ```python add_template(self, template: ResourceTemplate) -> None @@ -256,7 +308,7 @@ Add a resource template to the server. - `template`: A ResourceTemplate instance to add -#### `add_resource_fn` +#### `add_resource_fn` ```python add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None @@ -276,7 +328,7 @@ has parameters, it will be registered as a template resource. - `tags`: Optional set of tags for categorizing the resource -#### `resource` +#### `resource` ```python resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate] @@ -305,8 +357,36 @@ has parameters, it will be registered as a template resource. - `tags`: Optional set of tags for categorizing the resource - `enabled`: Optional boolean to enable or disable the resource +**Examples:** -#### `add_prompt` +Register a resource with a custom name: +```python +@server.resource("resource://my-resource") +def get_data() -> str: + return "Hello, world!" + +@server.resource("resource://my-resource") +async get_data() -> str: + data = await fetch_data() + return f"Hello, world! {data}" + +@server.resource("resource://{city}/weather") +def get_weather(city: str) -> str: + return f"Weather for {city}" + +@server.resource("resource://{city}/weather") +def get_weather_with_context(city: str, ctx: Context) -> str: + ctx.info(f"Fetching weather for {city}") + return f"Weather for {city}" + +@server.resource("resource://{city}/weather") +async def get_weather(city: str) -> str: + data = await fetch_weather(city) + return f"Weather for {city}: {data}" +``` + + +#### `add_prompt` ```python add_prompt(self, prompt: Prompt) -> None @@ -318,19 +398,19 @@ Add a prompt to the server. - `prompt`: A Prompt instance to add -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt ``` -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt] ``` -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt @@ -356,9 +436,11 @@ Decorator to register a prompt. tags: Optional set of tags for categorizing the prompt enabled: Optional boolean to enable or disable the prompt - Example: + Examples: + + ```python @server.prompt - def analyze_table(table_name: str) -> list\[Message]: + def analyze_table(table_name: str) -> list[Message]: schema = read_table_schema(table_name) return [ { @@ -369,7 +451,7 @@ Decorator to register a prompt. ] @server.prompt() - def analyze_with_context(table_name: str, ctx: Context) -> list\[Message]: + def analyze_with_context(table_name: str, ctx: Context) -> list[Message]: ctx.info(f"Analyzing table {table_name}") schema = read_table_schema(table_name) return [ @@ -381,7 +463,7 @@ Decorator to register a prompt. ] @server.prompt("custom_name") - def analyze_file(path: str) -> list\[Message]: + def analyze_file(path: str) -> list[Message]: content = await read_file(path) return [ { @@ -397,14 +479,15 @@ Decorator to register a prompt. ] @server.prompt(name="custom_name") - def another_prompt(data: str) -> list\[Message]: + def another_prompt(data: str) -> list[Message]: return [{"role": "user", "content": data}] # Direct function call server.prompt(my_function, name="custom_name") + ``` -#### `sse_app` +#### `sse_app` ```python sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan @@ -418,7 +501,7 @@ Create a Starlette app for the SSE server. - `middleware`: A list of middleware to apply to the app -#### `streamable_http_app` +#### `streamable_http_app` ```python streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan @@ -431,7 +514,7 @@ Create a Starlette app for the StreamableHTTP server. - `middleware`: A list of middleware to apply to the app -#### `http_app` +#### `http_app` ```python http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan @@ -448,7 +531,7 @@ Create a Starlette app using the specified HTTP transport. - A Starlette application configured with the specified transport -#### `mount` +#### `mount` ```python mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None @@ -502,7 +585,7 @@ automatically determined based on whether the server has a custom lifespan - `prompt_separator`: Deprecated. Separator character for prompt names. -#### `from_openapi` +#### `from_openapi` ```python from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI @@ -511,7 +594,7 @@ from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route Create a FastMCP server from an OpenAPI specification. -#### `from_fastapi` +#### `from_fastapi` ```python from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI @@ -520,7 +603,7 @@ from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] Create a FastMCP server from a FastAPI application. -#### `as_proxy` +#### `as_proxy` ```python as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy @@ -528,13 +611,13 @@ as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] Create a FastMCP proxy server for the given backend. -The ``backend`` argument can be either an existing :class:`~fastmcp.client.Client` -instance or any value accepted as the ``transport`` argument of -:class:`~fastmcp.client.Client`. This mirrors the convenience of the -``Client`` constructor. +The `backend` argument can be either an existing `fastmcp.client.Client` +instance or any value accepted as the `transport` argument of +`fastmcp.client.Client`. This mirrors the convenience of the +`fastmcp.client.Client` constructor. -#### `from_client` +#### `from_client` ```python from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy @@ -543,4 +626,4 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr Create a FastMCP proxy server from a FastMCP client. -### `MountedServer` +### `MountedServer` diff --git a/docs/python-sdk/fastmcp-tools-tool_transform.mdx b/docs/python-sdk/fastmcp-tools-tool_transform.mdx index 9fe9c13a4..6a7ea8ceb 100644 --- a/docs/python-sdk/fastmcp-tools-tool_transform.mdx +++ b/docs/python-sdk/fastmcp-tools-tool_transform.mdx @@ -18,38 +18,58 @@ descriptions, add default values, or hide them from clients while passing consta **Examples:** -# Rename argument 'old_name' to 'new_name' +Rename argument 'old_name' to 'new_name' +```python ArgTransform(name="new_name") +``` -# Change description only +Change description only +```python ArgTransform(description="Updated description") +``` -# Add a default value (makes argument optional) +Add a default value (makes argument optional) +```python ArgTransform(default=42) +``` -# Add a default factory (makes argument optional) +Add a default factory (makes argument optional) +```python ArgTransform(default_factory=lambda: time.time()) +``` -# Change the type +Change the type +```python ArgTransform(type=str) +``` -# Hide the argument entirely from clients +Hide the argument entirely from clients +```python ArgTransform(hide=True) +``` -# Hide argument but pass a constant value to parent +Hide argument but pass a constant value to parent +```python ArgTransform(hide=True, default="constant_value") +``` -# Hide argument but pass a factory-generated value to parent +Hide argument but pass a factory-generated value to parent +```python ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex) +``` -# Make an optional parameter required (removes any default) +Make an optional parameter required (removes any default) +```python ArgTransform(required=True) +``` -# Combine multiple transformations +Combine multiple transformations +```python ArgTransform(name="new_name", description="New desc", default=None, type=int) +``` -### `TransformedTool` +### `TransformedTool` A tool that is transformed from another tool. @@ -65,7 +85,7 @@ with transformed arguments. **Methods:** -#### `from_tool` +#### `from_tool` ```python from_tool(cls, tool: Tool, name: str | None = None, description: str | None = None, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None = None, serializer: Callable[[Any], str] | None = None, enabled: bool | None = None) -> TransformedTool @@ -81,9 +101,9 @@ argument names. - `name`: New name for the tool. Defaults to parent tool's name. - `transform_args`: Optional transformations for parent tool arguments. Only specified arguments are transformed, others pass through unchanged\: -- str\: Simple rename -- ArgTransform\: Complex transformation (rename/description/default/drop) -- None\: Drop the argument +- Simple rename (str) +- Complex transformation (rename/description/default/drop) (ArgTransform) +- Drop the argument (None) - `description`: New description. Defaults to parent's description. - `tags`: New tags. Defaults to parent's tags. - `annotations`: New annotations. Defaults to parent's annotations. @@ -92,17 +112,28 @@ Only specified arguments are transformed, others pass through unchanged\: **Returns:** - TransformedTool with the specified transformations. -Examples: -- # Transform specific arguments only -- Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged -- # Custom function with partial transforms -- async def custom(x: int, y: int) -> str: -result = await forward(x=x, y=y) -return f"Custom: {result}" -- Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"}) -- # Using **kwargs (gets all args, transformed and untransformed) -- async def flexible(**kwargs) -> str: -result = await forward(**kwargs) -return f"Got: {kwargs}" -- Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"}) +**Examples:** + +# Transform specific arguments only +```python +Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged +``` + +# Custom function with partial transforms +```python +async def custom(x: int, y: int) -> str: + result = await forward(x=x, y=y) + return f"Custom: {result}" + +Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"}) +``` + +# Using **kwargs (gets all args, transformed and untransformed) +```python +async def flexible(**kwargs) -> str: + result = await forward(**kwargs) + return f"Got: {kwargs}" + +Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"}) +``` diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 3a7685bfe..e3a7dd611 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -363,6 +363,7 @@ class FastMCP(Generic[LifespanResultT]): return await self._resource_manager.get_resource_templates() async def get_resource_template(self, key: str) -> ResourceTemplate: + """Get a registered resource template by key.""" templates = await self.get_resource_templates() if key not in templates: raise NotFoundError(f"Unknown resource template: {key}") @@ -403,9 +404,12 @@ class FastMCP(Generic[LifespanResultT]): include_in_schema: Whether to include in OpenAPI schema, defaults to True Example: + Register a custom HTTP route for a health check endpoint: + ```python @server.custom_route("/health", methods=["GET"]) async def health_check(request: Request) -> Response: return JSONResponse({"status": "ok"}) + ``` """ def decorator( @@ -814,15 +818,18 @@ class FastMCP(Generic[LifespanResultT]): name: Optional name for the tool (keyword-only, alternative to name_or_fn) description: Optional description of what the tool does tags: Optional set of tags for categorizing the tool - annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True}) + annotations: Optional annotations about the tool's behavior exclude_args: Optional list of argument names to exclude from the tool schema enabled: Optional boolean to enable or disable the tool - Example: + Examples: + Register a tool with a custom name: + ```python @server.tool def my_tool(x: int) -> str: return str(x) + # Register a tool with a custom name @server.tool def my_tool(x: int) -> str: return str(x) @@ -837,6 +844,7 @@ class FastMCP(Generic[LifespanResultT]): # Direct function call server.tool(my_function, name="custom_name") + ``` """ if isinstance(annotations, dict): annotations = ToolAnnotations(**annotations) @@ -991,7 +999,9 @@ class FastMCP(Generic[LifespanResultT]): tags: Optional set of tags for categorizing the resource enabled: Optional boolean to enable or disable the resource - Example: + Examples: + Register a resource with a custom name: + ```python @server.resource("resource://my-resource") def get_data() -> str: return "Hello, world!" @@ -1014,6 +1024,7 @@ class FastMCP(Generic[LifespanResultT]): async def get_weather(city: str) -> str: data = await fetch_weather(city) return f"Weather for {city}: {data}" + ``` """ # Check if user passed function directly instead of calling decorator if inspect.isroutine(uri): @@ -1138,7 +1149,9 @@ class FastMCP(Generic[LifespanResultT]): tags: Optional set of tags for categorizing the prompt enabled: Optional boolean to enable or disable the prompt - Example: + Examples: + + ```python @server.prompt def analyze_table(table_name: str) -> list[Message]: schema = read_table_schema(table_name) @@ -1182,6 +1195,7 @@ class FastMCP(Generic[LifespanResultT]): # Direct function call server.prompt(my_function, name="custom_name") + ``` """ if isinstance(name_or_fn, classmethod): @@ -1787,10 +1801,10 @@ class FastMCP(Generic[LifespanResultT]): ) -> FastMCPProxy: """Create a FastMCP proxy server for the given backend. - The ``backend`` argument can be either an existing :class:`~fastmcp.client.Client` - instance or any value accepted as the ``transport`` argument of - :class:`~fastmcp.client.Client`. This mirrors the convenience of the - ``Client`` constructor. + The `backend` argument can be either an existing `fastmcp.client.Client` + instance or any value accepted as the `transport` argument of + `fastmcp.client.Client`. This mirrors the convenience of the + `fastmcp.client.Client` constructor. """ from fastmcp.client.client import Client from fastmcp.server.proxy import FastMCPProxy @@ -1827,14 +1841,14 @@ class FastMCP(Generic[LifespanResultT]): Given a component, determine if it should be enabled. Returns True if it should be enabled; False if it should not. Rules: - • If the component's enabled property is False, always return False. - • If both include_tags and exclude_tags are None, return True. - • If exclude_tags is provided, check each exclude tag: + - If the component's enabled property is False, always return False. + - If both include_tags and exclude_tags are None, return True. + - If exclude_tags is provided, check each exclude tag: - If the exclude tag is a string, it must be present in the input tags to exclude. - • If include_tags is provided, check each include tag: + - If include_tags is provided, check each include tag: - If the include tag is a string, it must be present in the input tags to include. - • If include_tags is provided and none of the include tags match, return False. - • If include_tags is not provided, return True. + - If include_tags is provided and none of the include tags match, return False. + - If include_tags is not provided, return True. """ if not component.enabled: return False @@ -1875,12 +1889,21 @@ def add_resource_prefix( The resource URI with the prefix added Examples: - >>> add_resource_prefix("resource://path/to/resource", "prefix") - "resource://prefix/path/to/resource" # with new style - >>> add_resource_prefix("resource://path/to/resource", "prefix") - "prefix+resource://path/to/resource" # with legacy style - >>> add_resource_prefix("resource:///absolute/path", "prefix") - "resource://prefix//absolute/path" # with new style + With new style: + ```python + add_resource_prefix("resource://path/to/resource", "prefix") + "resource://prefix/path/to/resource" + ``` + With legacy style: + ```python + add_resource_prefix("resource://path/to/resource", "prefix") + "prefix+resource://path/to/resource" + ``` + With absolute path: + ```python + add_resource_prefix("resource:///absolute/path", "prefix") + "resource://prefix//absolute/path" + ``` Raises: ValueError: If the URI doesn't match the expected protocol://path format @@ -1926,12 +1949,21 @@ def remove_resource_prefix( The resource URI with the prefix removed Examples: - >>> remove_resource_prefix("resource://prefix/path/to/resource", "prefix") - "resource://path/to/resource" # with new style - >>> remove_resource_prefix("prefix+resource://path/to/resource", "prefix") - "resource://path/to/resource" # with legacy style - >>> remove_resource_prefix("resource://prefix//absolute/path", "prefix") - "resource:///absolute/path" # with new style + With new style: + ```python + remove_resource_prefix("resource://prefix/path/to/resource", "prefix") + "resource://path/to/resource" + ``` + With legacy style: + ```python + remove_resource_prefix("prefix+resource://path/to/resource", "prefix") + "resource://path/to/resource" + ``` + With absolute path: + ```python + remove_resource_prefix("resource://prefix//absolute/path", "prefix") + "resource:///absolute/path" + ``` Raises: ValueError: If the URI doesn't match the expected protocol://path format @@ -1984,12 +2016,21 @@ def has_resource_prefix( True if the URI has the specified prefix, False otherwise Examples: - >>> has_resource_prefix("resource://prefix/path/to/resource", "prefix") - True # with new style - >>> has_resource_prefix("prefix+resource://path/to/resource", "prefix") - True # with legacy style - >>> has_resource_prefix("resource://other/path/to/resource", "prefix") + With new style: + ```python + has_resource_prefix("resource://prefix/path/to/resource", "prefix") + True + ``` + With legacy style: + ```python + has_resource_prefix("prefix+resource://path/to/resource", "prefix") + True + ``` + With other path: + ```python + has_resource_prefix("resource://other/path/to/resource", "prefix") False + ``` Raises: ValueError: If the URI doesn't match the expected protocol://path format diff --git a/src/fastmcp/tools/tool_transform.py b/src/fastmcp/tools/tool_transform.py index 149469a4c..c6145d807 100644 --- a/src/fastmcp/tools/tool_transform.py +++ b/src/fastmcp/tools/tool_transform.py @@ -100,35 +100,55 @@ class ArgTransform: examples: Examples for the argument. Use ... for no change. Examples: - # Rename argument 'old_name' to 'new_name' + Rename argument 'old_name' to 'new_name' + ```python ArgTransform(name="new_name") + ``` - # Change description only + Change description only + ```python ArgTransform(description="Updated description") + ``` - # Add a default value (makes argument optional) + Add a default value (makes argument optional) + ```python ArgTransform(default=42) + ``` - # Add a default factory (makes argument optional) + Add a default factory (makes argument optional) + ```python ArgTransform(default_factory=lambda: time.time()) + ``` - # Change the type + Change the type + ```python ArgTransform(type=str) + ``` - # Hide the argument entirely from clients + Hide the argument entirely from clients + ```python ArgTransform(hide=True) + ``` - # Hide argument but pass a constant value to parent + Hide argument but pass a constant value to parent + ```python ArgTransform(hide=True, default="constant_value") + ``` - # Hide argument but pass a factory-generated value to parent + Hide argument but pass a factory-generated value to parent + ```python ArgTransform(hide=True, default_factory=lambda: uuid.uuid4().hex) + ``` - # Make an optional parameter required (removes any default) + Make an optional parameter required (removes any default) + ```python ArgTransform(required=True) + ``` - # Combine multiple transformations + Combine multiple transformations + ```python ArgTransform(name="new_name", description="New desc", default=None, type=int) + ``` """ name: str | EllipsisType = NotSet @@ -279,9 +299,9 @@ class TransformedTool(Tool): name: New name for the tool. Defaults to parent tool's name. transform_args: Optional transformations for parent tool arguments. Only specified arguments are transformed, others pass through unchanged: - - str: Simple rename - - ArgTransform: Complex transformation (rename/description/default/drop) - - None: Drop the argument + - Simple rename (str) + - Complex transformation (rename/description/default/drop) (ArgTransform) + - Drop the argument (None) description: New description. Defaults to parent's description. tags: New tags. Defaults to parent's tags. annotations: New annotations. Defaults to parent's annotations. @@ -290,23 +310,29 @@ class TransformedTool(Tool): Returns: TransformedTool with the specified transformations. - Examples: + Examples: # Transform specific arguments only + ```python Tool.from_tool(parent, transform_args={"old": "new"}) # Others unchanged + ``` # Custom function with partial transforms + ```python async def custom(x: int, y: int) -> str: result = await forward(x=x, y=y) return f"Custom: {result}" Tool.from_tool(parent, transform_fn=custom, transform_args={"a": "x", "b": "y"}) + ``` # Using **kwargs (gets all args, transformed and untransformed) + ```python async def flexible(**kwargs) -> str: result = await forward(**kwargs) return f"Got: {kwargs}" Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"}) + ``` """ transform_args = transform_args or {} @@ -423,8 +449,8 @@ class TransformedTool(Tool): Returns: A tuple containing: - - dict: The new JSON schema for the transformed tool - - Callable: Async function that validates and forwards calls to the parent tool + - The new JSON schema for the transformed tool as a dictionary + - Async function that validates and forwards calls to the parent tool """ # Build transformed schema and mapping