diff --git a/docs/docs.json b/docs/docs.json
index 4c3e5e153..c8d1ea394 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -73,7 +73,10 @@
{
"group": "Essentials",
"icon": "cube",
- "pages": ["servers/server", "deployment/running-server"]
+ "pages": [
+ "servers/server",
+ "deployment/running-server"
+ ]
},
{
"group": "Core Components",
@@ -116,7 +119,10 @@
{
"group": "Essentials",
"icon": "cube",
- "pages": ["clients/client", "clients/transports"]
+ "pages": [
+ "clients/client",
+ "clients/transports"
+ ]
},
{
"group": "Core Operations",
@@ -142,7 +148,10 @@
{
"group": "Authentication",
"icon": "user-shield",
- "pages": ["clients/auth/oauth", "clients/auth/bearer"]
+ "pages": [
+ "clients/auth/oauth",
+ "clients/auth/bearer"
+ ]
}
]
},
@@ -188,12 +197,17 @@
},
{
"anchor": "What's New",
- "pages": ["updates", "changelog"]
+ "pages": [
+ "updates",
+ "changelog"
+ ]
},
{
"anchor": "Community",
"icon": "users",
- "pages": ["community/showcase"]
+ "pages": [
+ "community/showcase"
+ ]
}
]
},
diff --git a/docs/python-sdk/fastmcp-client-transports.mdx b/docs/python-sdk/fastmcp-client-transports.mdx
index 13d6a3818..e7fb245f8 100644
--- a/docs/python-sdk/fastmcp-client-transports.mdx
+++ b/docs/python-sdk/fastmcp-client-transports.mdx
@@ -7,7 +7,7 @@ sidebarTitle: transports
## Functions
-### `infer_transport`
+### `infer_transport`
```python
infer_transport(transport: ClientTransport | FastMCP | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str) -> ClientTransport
@@ -161,61 +161,61 @@ transports like Python, Node, Uvx, etc.
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-#### `connect`
+#### `connect`
```python
connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
```
-#### `disconnect`
+#### `disconnect`
```python
disconnect(self)
```
-#### `close`
+#### `close`
```python
close(self)
```
-### `PythonStdioTransport`
+### `PythonStdioTransport`
Transport for running Python scripts.
-### `FastMCPStdioTransport`
+### `FastMCPStdioTransport`
Transport for running FastMCP servers using the FastMCP CLI.
-### `NodeStdioTransport`
+### `NodeStdioTransport`
Transport for running Node.js scripts.
-### `UvStdioTransport`
+### `UvStdioTransport`
Transport for running commands via the uv tool.
-### `UvxStdioTransport`
+### `UvxStdioTransport`
Transport for running commands via the uvx tool.
-### `NpxStdioTransport`
+### `NpxStdioTransport`
Transport for running commands via the npx tool.
-### `FastMCPTransport`
+### `FastMCPTransport`
In-memory transport for FastMCP servers.
@@ -228,13 +228,13 @@ tests or scenarios where client and server run in the same runtime.
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
```
-### `MCPConfigTransport`
+### `MCPConfigTransport`
Transport for connecting to one or more MCP servers defined in an MCPConfig.
@@ -287,7 +287,7 @@ async with client:
**Methods:**
-#### `connect_session`
+#### `connect_session`
```python
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
diff --git a/docs/python-sdk/fastmcp-tools-tool.mdx b/docs/python-sdk/fastmcp-tools-tool.mdx
index 2aaf97cc3..f7d1ff55a 100644
--- a/docs/python-sdk/fastmcp-tools-tool.mdx
+++ b/docs/python-sdk/fastmcp-tools-tool.mdx
@@ -7,7 +7,7 @@ sidebarTitle: tool
## Functions
-### `default_serializer`
+### `default_serializer`
```python
default_serializer(data: Any) -> str
@@ -15,17 +15,17 @@ default_serializer(data: Any) -> str
## Classes
-### `ToolResult`
+### `ToolResult`
**Methods:**
-#### `to_mcp_result`
+#### `to_mcp_result`
```python
to_mcp_result(self) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]
```
-### `Tool`
+### `Tool`
Internal tool registration info.
@@ -33,34 +33,34 @@ Internal tool registration info.
**Methods:**
-#### `enable`
+#### `enable`
```python
enable(self) -> None
```
-#### `disable`
+#### `disable`
```python
disable(self) -> None
```
-#### `to_mcp_tool`
+#### `to_mcp_tool`
```python
to_mcp_tool(self, **overrides: Any) -> MCPTool
```
-#### `from_function`
+#### `from_function`
```python
-from_function(fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT = NotSet, serializer: Callable[[Any], str] | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
+from_function(fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: Callable[[Any], str] | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
```
Create a Tool from a function.
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any]) -> ToolResult
@@ -75,26 +75,26 @@ implemented by subclasses.
(list of ContentBlocks, dict of structured output).
-#### `from_tool`
+#### `from_tool`
```python
from_tool(cls, tool: Tool) -> TransformedTool
```
-### `FunctionTool`
+### `FunctionTool`
**Methods:**
-#### `from_function`
+#### `from_function`
```python
-from_function(cls, fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT = NotSet, serializer: Callable[[Any], str] | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
+from_function(cls, fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: Callable[[Any], str] | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
```
Create a Tool from a function.
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any]) -> ToolResult
@@ -103,11 +103,11 @@ run(self, arguments: dict[str, Any]) -> ToolResult
Run the tool with arguments.
-### `ParsedFunction`
+### `ParsedFunction`
**Methods:**
-#### `from_function`
+#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], exclude_args: list[str] | None = None, validate: bool = True, wrap_non_object_output_schema: bool = True) -> ParsedFunction
diff --git a/docs/python-sdk/fastmcp-tools-tool_transform.mdx b/docs/python-sdk/fastmcp-tools-tool_transform.mdx
index b0a891fc2..5cfed788b 100644
--- a/docs/python-sdk/fastmcp-tools-tool_transform.mdx
+++ b/docs/python-sdk/fastmcp-tools-tool_transform.mdx
@@ -7,7 +7,7 @@ sidebarTitle: tool_transform
## Functions
-### `forward`
+### `forward`
```python
forward(**kwargs) -> ToolResult
@@ -36,7 +36,7 @@ tool has args `a` and `b`, and an `transform_args` was provided that maps `x` to
- `TypeError`: If provided arguments don't match the transformed schema.
-### `forward_raw`
+### `forward_raw`
```python
forward_raw(**kwargs) -> ToolResult
@@ -62,7 +62,7 @@ y=2)` will call the parent tool with `x=1` and `y=2`.
- `RuntimeError`: If called outside a transformed tool context.
-### `apply_transformations_to_tools`
+### `apply_transformations_to_tools`
```python
apply_transformations_to_tools(tools: dict[str, Tool], transformations: dict[str, ToolTransformConfig]) -> dict[str, Tool]
@@ -75,7 +75,7 @@ are left unchanged.
## Classes
-### `ArgTransform`
+### `ArgTransform`
Configuration for transforming a parent tool's argument.
@@ -137,7 +137,7 @@ ArgTransform(name="new_name", description="New desc", default=None, type=int)
```
-### `ArgTransformConfig`
+### `ArgTransformConfig`
A model for requesting a single argument transform.
@@ -145,7 +145,7 @@ A model for requesting a single argument transform.
**Methods:**
-#### `to_arg_transform`
+#### `to_arg_transform`
```python
to_arg_transform(self) -> ArgTransform
@@ -154,7 +154,7 @@ to_arg_transform(self) -> ArgTransform
Convert the argument transform to a FastMCP argument transform.
-### `TransformedTool`
+### `TransformedTool`
A tool that is transformed from another tool.
@@ -171,7 +171,7 @@ inherited from the parent tool but can be overridden or disabled.
**Methods:**
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any]) -> ToolResult
@@ -190,10 +190,10 @@ functions.
- ToolResult object containing content and optional structured output.
-#### `from_tool`
+#### `from_tool`
```python
-from_tool(cls, tool: Tool, name: str | None = None, title: str | None | NotSetT = NotSet, description: str | None | NotSetT = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None | NotSetT = NotSet, output_schema: dict[str, Any] | None | NotSetT = NotSet, serializer: Callable[[Any], str] | None | NotSetT = NotSet, meta: dict[str, Any] | None | NotSetT = NotSet, enabled: bool | None = None) -> TransformedTool
+from_tool(cls, tool: Tool, name: str | None = None, title: str | None | NotSetT = NotSet, description: str | None | NotSetT = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None | NotSetT = NotSet, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: Callable[[Any], str] | None | NotSetT = NotSet, meta: dict[str, Any] | None | NotSetT = NotSet, enabled: bool | None = None) -> TransformedTool
```
Create a transformed tool from a parent tool.
@@ -272,7 +272,7 @@ async def custom_output(**kwargs) -> ToolResult:
```
-### `ToolTransformConfig`
+### `ToolTransformConfig`
Provides a way to transform a tool.
@@ -280,7 +280,7 @@ Provides a way to transform a tool.
**Methods:**
-#### `apply`
+#### `apply`
```python
apply(self, tool: Tool) -> TransformedTool
diff --git a/src/fastmcp/client/transports.py b/src/fastmcp/client/transports.py
index b334baa73..2d1c212b2 100644
--- a/src/fastmcp/client/transports.py
+++ b/src/fastmcp/client/transports.py
@@ -346,8 +346,7 @@ class StdioTransport(ClientTransport):
) -> AsyncIterator[ClientSession]:
try:
await self.connect(**session_kwargs)
- assert self._session is not None
- yield self._session
+ yield cast(ClientSession, self._session)
finally:
if not self.keep_alive:
await self.disconnect()
diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py
index bb0c8afd5..fcbb3a5ce 100644
--- a/src/fastmcp/tools/tool.py
+++ b/src/fastmcp/tools/tool.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import inspect
+import warnings
from collections.abc import Callable
from dataclasses import dataclass
from typing import (
@@ -8,6 +9,7 @@ from typing import (
Annotated,
Any,
Generic,
+ Literal,
TypeVar,
get_type_hints,
)
@@ -18,6 +20,7 @@ from mcp.types import ContentBlock, TextContent, ToolAnnotations
from mcp.types import Tool as MCPTool
from pydantic import Field, PydanticSchemaGenerationError
+import fastmcp
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
@@ -162,7 +165,7 @@ class Tool(FastMCPComponent):
tags: set[str] | None = None,
annotations: ToolAnnotations | None = None,
exclude_args: list[str] | None = None,
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
+ output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
serializer: Callable[[Any], str] | None = None,
meta: dict[str, Any] | None = None,
enabled: bool | None = None,
@@ -204,7 +207,7 @@ class Tool(FastMCPComponent):
description: str | None | NotSetT = NotSet,
tags: set[str] | None = None,
annotations: ToolAnnotations | None | NotSetT = NotSet,
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
+ output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
serializer: Callable[[Any], str] | None = None,
meta: dict[str, Any] | None | NotSetT = NotSet,
transform_args: dict[str, ArgTransform] | None = None,
@@ -242,7 +245,7 @@ class FunctionTool(Tool):
tags: set[str] | None = None,
annotations: ToolAnnotations | None = None,
exclude_args: list[str] | None = None,
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
+ output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
serializer: Callable[[Any], str] | None = None,
meta: dict[str, Any] | None = None,
enabled: bool | None = None,
@@ -255,16 +258,26 @@ class FunctionTool(Tool):
raise ValueError("You must provide a name for lambda functions")
if isinstance(output_schema, NotSetT):
- output_schema = parsed_fn.output_schema
+ final_output_schema = parsed_fn.output_schema
elif output_schema is False:
- output_schema = None
+ # Handle False as deprecated synonym for None (deprecated in 2.11.4)
+ if fastmcp.settings.deprecation_warnings:
+ warnings.warn(
+ "Passing output_schema=False is deprecated. Use output_schema=None instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ final_output_schema = None
+ else:
+ # At this point output_schema is not NotSetT and not False, so it must be dict | None
+ final_output_schema = output_schema
# Note: explicit schemas (dict) are used as-is without auto-wrapping
# Validate that explicit schemas are object type for structured content
- if output_schema is not None and isinstance(output_schema, dict):
- if output_schema.get("type") != "object":
+ if final_output_schema is not None and isinstance(final_output_schema, dict):
+ if final_output_schema.get("type") != "object":
raise ValueError(
- f'Output schemas must have "type" set to "object" due to MCP spec limitations. Received: {output_schema!r}'
+ f'Output schemas must have "type" set to "object" due to MCP spec limitations. Received: {final_output_schema!r}'
)
return cls(
@@ -273,7 +286,7 @@ class FunctionTool(Tool):
title=title,
description=description or parsed_fn.description,
parameters=parsed_fn.input_schema,
- output_schema=output_schema,
+ output_schema=final_output_schema,
annotations=annotations,
tags=tags or set(),
serializer=serializer,
diff --git a/src/fastmcp/tools/tool_transform.py b/src/fastmcp/tools/tool_transform.py
index 7a34efa60..e4f867ab2 100644
--- a/src/fastmcp/tools/tool_transform.py
+++ b/src/fastmcp/tools/tool_transform.py
@@ -1,10 +1,11 @@
from __future__ import annotations
import inspect
+import warnings
from collections.abc import Callable
from contextvars import ContextVar
from dataclasses import dataclass
-from typing import Annotated, Any, Literal
+from typing import Annotated, Any, Literal, cast
import pydantic_core
from mcp.types import ToolAnnotations
@@ -12,6 +13,7 @@ from pydantic import ConfigDict
from pydantic.fields import Field
from pydantic.functional_validators import BeforeValidator
+import fastmcp
from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
from fastmcp.utilities.components import _convert_set_default_none
from fastmcp.utilities.json_schema import compress_schema
@@ -369,7 +371,7 @@ class TransformedTool(Tool):
transform_fn: Callable[..., Any] | None = None,
transform_args: dict[str, ArgTransform] | None = None,
annotations: ToolAnnotations | None | NotSetT = NotSet,
- output_schema: dict[str, Any] | None | NotSetT = NotSet,
+ output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
serializer: Callable[[Any], str] | None | NotSetT = NotSet,
meta: dict[str, Any] | None | NotSetT = NotSet,
enabled: bool | None = None,
@@ -471,8 +473,8 @@ class TransformedTool(Tool):
if output_schema is NotSet:
# Use smart fallback: try custom function, then parent
if transform_fn is not None:
- assert parsed_fn is not None
- final_output_schema = parsed_fn.output_schema
+ # parsed fn is not none here
+ final_output_schema = cast(ParsedFunction, parsed_fn).output_schema
if final_output_schema is None:
# Check if function returns ToolResult - if so, don't fall back to parent
return_annotation = inspect.signature(
@@ -484,16 +486,25 @@ class TransformedTool(Tool):
final_output_schema = tool.output_schema
else:
final_output_schema = tool.output_schema
+ elif output_schema is False:
+ # Handle False as deprecated synonym for None (deprecated in 2.11.4)
+ if fastmcp.settings.deprecation_warnings:
+ warnings.warn(
+ "Passing output_schema=False is deprecated. Use output_schema=None instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ final_output_schema = None
else:
- assert isinstance(output_schema, dict | None)
- final_output_schema = output_schema
+ final_output_schema = cast(dict | None, output_schema)
if transform_fn is None:
# User wants pure transformation - use forwarding_fn as the main function
final_fn = forwarding_fn
final_schema = schema
else:
- assert parsed_fn is not None
+ # parsed fn is not none here
+ parsed_fn = cast(ParsedFunction, parsed_fn)
# User provided custom function - merge schemas
final_fn = transform_fn
diff --git a/tests/deprecated/test_output_schema_false.py b/tests/deprecated/test_output_schema_false.py
new file mode 100644
index 000000000..0dcc20d06
--- /dev/null
+++ b/tests/deprecated/test_output_schema_false.py
@@ -0,0 +1,139 @@
+"""Test deprecated output_schema=False behavior (deprecated in 2.11.4)."""
+
+import warnings
+
+import pytest
+
+from fastmcp import FastMCP
+from fastmcp.tools import Tool
+
+
+class TestDeprecatedOutputSchemaFalse:
+ """Test that output_schema=False is deprecated but still works."""
+
+ async def test_tool_decorator_output_schema_false_deprecated(self):
+ """Test that @mcp.tool(output_schema=False) shows deprecation warning."""
+ mcp = FastMCP()
+
+ with pytest.warns(
+ DeprecationWarning, match="output_schema=False is deprecated"
+ ):
+
+ @mcp.tool(output_schema=False) # type: ignore[arg-type]
+ def simple_tool() -> int:
+ """A simple tool."""
+ return 42
+
+ # Verify the tool was created with None as output_schema
+ tool = mcp._tool_manager._tools["simple_tool"]
+ assert tool.output_schema is None
+
+ async def test_tool_from_function_output_schema_false_deprecated(self):
+ """Test that Tool.from_function(output_schema=False) shows deprecation warning."""
+
+ def my_function() -> str:
+ """A simple function."""
+ return "hello"
+
+ with pytest.warns(
+ DeprecationWarning, match="output_schema=False is deprecated"
+ ):
+ tool = Tool.from_function(my_function, output_schema=False) # type: ignore[arg-type]
+
+ # Verify the tool was created with None as output_schema
+ assert tool.output_schema is None
+
+ async def test_tool_from_tool_output_schema_false_deprecated(self):
+ """Test that Tool.from_tool(output_schema=False) shows deprecation warning."""
+
+ # Create a parent tool
+ def parent_function() -> dict[str, str]:
+ """A parent function."""
+ return {"status": "ok"}
+
+ parent_tool = Tool.from_function(parent_function)
+
+ with pytest.warns(
+ DeprecationWarning, match="output_schema=False is deprecated"
+ ):
+ transformed_tool = Tool.from_tool(parent_tool, output_schema=False) # type: ignore[arg-type]
+
+ # Verify the tool was created with None as output_schema
+ assert transformed_tool.output_schema is None
+
+ async def test_output_schema_false_functionality_preserved(self):
+ """Test that output_schema=False still works functionally like output_schema=None."""
+ mcp = FastMCP()
+
+ # Create two tools - one with False, one with None
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ @mcp.tool(output_schema=False) # type: ignore[arg-type]
+ def tool_with_false() -> dict[str, str]:
+ """Tool with output_schema=False."""
+ return {"result": "false"}
+
+ @mcp.tool(output_schema=None)
+ def tool_with_none() -> dict[str, str]:
+ """Tool with output_schema=None."""
+ return {"result": "none"}
+
+ # Both should have None as output_schema
+ assert mcp._tool_manager._tools["tool_with_false"].output_schema is None
+ assert mcp._tool_manager._tools["tool_with_none"].output_schema is None
+
+ # Both should work the same way
+ result_false = await mcp._tool_manager._tools["tool_with_false"].run({})
+ result_none = await mcp._tool_manager._tools["tool_with_none"].run({})
+
+ # Both should return structured content for dict-like objects
+ assert result_false.structured_content == {"result": "false"}
+ assert result_none.structured_content == {"result": "none"}
+
+ async def test_output_schema_false_with_scalar_return(self):
+ """Test that output_schema=False works with scalar returns (no structured content)."""
+ mcp = FastMCP()
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ @mcp.tool(output_schema=False) # type: ignore[arg-type]
+ def scalar_tool() -> int:
+ """Tool returning a scalar."""
+ return 42
+
+ tool = mcp._tool_manager._tools["scalar_tool"]
+ assert tool.output_schema is None
+
+ result = await tool.run({})
+ # Scalar values don't produce structured content
+ assert result.structured_content is None
+ assert len(result.content) == 1
+ assert result.content[0].text == "42" # type: ignore[attr-defined]
+
+ async def test_transform_with_output_schema_false(self):
+ """Test that transformation with output_schema=False still works."""
+
+ # Create a parent tool
+ def parent_function(x: int) -> dict[str, int]:
+ """A parent function."""
+ return {"value": x * 2}
+
+ parent_tool = Tool.from_function(parent_function)
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ # Transform with output_schema=False
+ transformed = Tool.from_tool(
+ parent_tool,
+ name="doubled",
+ output_schema=False, # type: ignore[arg-type]
+ )
+
+ assert transformed.output_schema is None
+
+ # Tool should still work
+ result = await transformed.run({"x": 5})
+ assert result.structured_content == {"value": 10}