diff --git a/docs/python-sdk/fastmcp-prompts-function_prompt.mdx b/docs/python-sdk/fastmcp-prompts-function_prompt.mdx
index a3222afc0..1efd31223 100644
--- a/docs/python-sdk/fastmcp-prompts-function_prompt.mdx
+++ b/docs/python-sdk/fastmcp-prompts-function_prompt.mdx
@@ -10,7 +10,7 @@ Standalone @prompt decorator for FastMCP.
## Functions
-### `prompt`
+### `prompt`
```python
prompt(name_or_fn: str | Callable[..., Any] | None = None) -> Any
@@ -25,19 +25,19 @@ using mcp.add_prompt().
## Classes
-### `DecoratedPrompt`
+### `DecoratedPrompt`
Protocol for functions decorated with @prompt.
-### `PromptMeta`
+### `PromptMeta`
Metadata attached to functions by the @prompt decorator.
-### `FunctionPrompt`
+### `FunctionPrompt`
A prompt that is a function.
@@ -45,7 +45,7 @@ A prompt that is a function.
**Methods:**
-#### `from_function`
+#### `from_function`
```python
from_function(cls, fn: Callable[..., Any]) -> FunctionPrompt
@@ -66,7 +66,7 @@ The function can return:
- PromptResult: used directly
-#### `render`
+#### `render`
```python
render(self, arguments: dict[str, Any] | None = None) -> PromptResult
@@ -75,7 +75,7 @@ render(self, arguments: dict[str, Any] | None = None) -> PromptResult
Render the prompt with arguments.
-#### `register_with_docket`
+#### `register_with_docket`
```python
register_with_docket(self, docket: Docket) -> None
@@ -87,7 +87,7 @@ FunctionPrompt registers the underlying function, which has the user's
Depends parameters for docket to resolve.
-#### `add_to_docket`
+#### `add_to_docket`
```python
add_to_docket(self, docket: Docket, arguments: dict[str, Any] | None, **kwargs: Any) -> Execution
diff --git a/docs/python-sdk/fastmcp-resources-function_resource.mdx b/docs/python-sdk/fastmcp-resources-function_resource.mdx
index 3a7d346e1..964000021 100644
--- a/docs/python-sdk/fastmcp-resources-function_resource.mdx
+++ b/docs/python-sdk/fastmcp-resources-function_resource.mdx
@@ -10,7 +10,7 @@ Standalone @resource decorator for FastMCP.
## Functions
-### `resource`
+### `resource`
```python
resource(uri: str) -> Callable[[F], F]
@@ -25,19 +25,19 @@ using mcp.add_resource().
## Classes
-### `DecoratedResource`
+### `DecoratedResource`
Protocol for functions decorated with @resource.
-### `ResourceMeta`
+### `ResourceMeta`
Metadata attached to functions by the @resource decorator.
-### `FunctionResource`
+### `FunctionResource`
A resource that defers data loading by wrapping a function.
@@ -54,7 +54,7 @@ The function can return:
**Methods:**
-#### `from_function`
+#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], uri: str | AnyUrl | None = None) -> FunctionResource
@@ -71,7 +71,7 @@ individual parameters must not be passed.
Cannot be used together with metadata parameter.
-#### `read`
+#### `read`
```python
read(self) -> str | bytes | ResourceResult
@@ -80,7 +80,7 @@ read(self) -> str | bytes | ResourceResult
Read the resource by calling the wrapped function.
-#### `register_with_docket`
+#### `register_with_docket`
```python
register_with_docket(self, docket: Docket) -> None
diff --git a/docs/python-sdk/fastmcp-resources-template.mdx b/docs/python-sdk/fastmcp-resources-template.mdx
index 89e51c22f..3f95837a9 100644
--- a/docs/python-sdk/fastmcp-resources-template.mdx
+++ b/docs/python-sdk/fastmcp-resources-template.mdx
@@ -10,7 +10,7 @@ Resource template functionality.
## Functions
-### `extract_query_params`
+### `extract_query_params`
```python
extract_query_params(uri_template: str) -> set[str]
@@ -20,7 +20,7 @@ extract_query_params(uri_template: str) -> set[str]
Extract query parameter names from RFC 6570 `{?param1,param2}` syntax.
-### `build_regex`
+### `build_regex`
```python
build_regex(template: str) -> re.Pattern
@@ -35,7 +35,7 @@ Supports:
- `{?var1,var2}` - query parameters (ignored in path matching)
-### `match_uri_template`
+### `match_uri_template`
```python
match_uri_template(uri: str, uri_template: str) -> dict[str, str] | None
@@ -51,7 +51,7 @@ Supports RFC 6570 URI templates:
## Classes
-### `ResourceTemplate`
+### `ResourceTemplate`
A template for dynamically creating resources.
@@ -59,13 +59,13 @@ A template for dynamically creating resources.
**Methods:**
-#### `from_function`
+#### `from_function`
```python
from_function(fn: Callable[..., Any], uri_template: str, name: str | None = None, version: str | int | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, mime_type: str | None = None, tags: set[str] | None = None, annotations: Annotations | None = None, meta: dict[str, Any] | None = None, task: bool | TaskConfig | None = None, auth: AuthCheck | list[AuthCheck] | None = None) -> FunctionResourceTemplate
```
-#### `set_default_mime_type`
+#### `set_default_mime_type`
```python
set_default_mime_type(cls, mime_type: str | None) -> str
@@ -74,7 +74,7 @@ set_default_mime_type(cls, mime_type: str | None) -> str
Set default MIME type if not provided.
-#### `matches`
+#### `matches`
```python
matches(self, uri: str) -> dict[str, Any] | None
@@ -83,7 +83,7 @@ matches(self, uri: str) -> dict[str, Any] | None
Check if URI matches template and extract parameters.
-#### `read`
+#### `read`
```python
read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
@@ -92,7 +92,7 @@ read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
Read the resource content.
-#### `convert_result`
+#### `convert_result`
```python
convert_result(self, raw_value: Any) -> ResourceResult
@@ -108,7 +108,7 @@ Handles ResourceResult passthrough and converts raw values using
ResourceResult's normalization.
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any]) -> Resource
@@ -120,7 +120,7 @@ The base implementation does not support background tasks.
Use FunctionResourceTemplate for task support.
-#### `to_mcp_template`
+#### `to_mcp_template`
```python
to_mcp_template(self, **overrides: Any) -> SDKResourceTemplate
@@ -129,7 +129,7 @@ to_mcp_template(self, **overrides: Any) -> SDKResourceTemplate
Convert the resource template to an SDKResourceTemplate.
-#### `from_mcp_template`
+#### `from_mcp_template`
```python
from_mcp_template(cls, mcp_template: SDKResourceTemplate) -> ResourceTemplate
@@ -138,7 +138,7 @@ from_mcp_template(cls, mcp_template: SDKResourceTemplate) -> ResourceTemplate
Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object.
-#### `key`
+#### `key`
```python
key(self) -> str
@@ -147,7 +147,7 @@ key(self) -> str
The globally unique lookup key for this template.
-#### `register_with_docket`
+#### `register_with_docket`
```python
register_with_docket(self, docket: Docket) -> None
@@ -156,7 +156,7 @@ register_with_docket(self, docket: Docket) -> None
Register this template with docket for background execution.
-#### `add_to_docket`
+#### `add_to_docket`
```python
add_to_docket(self, docket: Docket, params: dict[str, Any], **kwargs: Any) -> Execution
@@ -172,13 +172,13 @@ Schedule this template for background execution via docket.
- `**kwargs`: Additional kwargs passed to docket.add()
-#### `get_span_attributes`
+#### `get_span_attributes`
```python
get_span_attributes(self) -> dict[str, Any]
```
-### `FunctionResourceTemplate`
+### `FunctionResourceTemplate`
A template for dynamically creating resources.
@@ -186,7 +186,7 @@ A template for dynamically creating resources.
**Methods:**
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any]) -> Resource
@@ -195,7 +195,7 @@ create_resource(self, uri: str, params: dict[str, Any]) -> Resource
Create a resource from the template with the given parameters.
-#### `read`
+#### `read`
```python
read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
@@ -204,7 +204,7 @@ read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
Read the resource content.
-#### `register_with_docket`
+#### `register_with_docket`
```python
register_with_docket(self, docket: Docket) -> None
@@ -216,7 +216,7 @@ FunctionResourceTemplate registers the underlying function, which has the
user's Depends parameters for docket to resolve.
-#### `add_to_docket`
+#### `add_to_docket`
```python
add_to_docket(self, docket: Docket, params: dict[str, Any], **kwargs: Any) -> Execution
@@ -234,7 +234,7 @@ FunctionResourceTemplate splats the params dict since .fn expects **kwargs.
- `**kwargs`: Additional kwargs passed to docket.add()
-#### `from_function`
+#### `from_function`
```python
from_function(cls, fn: Callable[..., Any], uri_template: str, name: str | None = None, version: str | int | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, mime_type: str | None = None, tags: set[str] | None = None, annotations: Annotations | None = None, meta: dict[str, Any] | None = None, task: bool | TaskConfig | None = None, auth: AuthCheck | list[AuthCheck] | None = None) -> FunctionResourceTemplate
diff --git a/docs/python-sdk/fastmcp-server-dependencies.mdx b/docs/python-sdk/fastmcp-server-dependencies.mdx
index 88a756ebc..f00d34392 100644
--- a/docs/python-sdk/fastmcp-server-dependencies.mdx
+++ b/docs/python-sdk/fastmcp-server-dependencies.mdx
@@ -156,7 +156,7 @@ Tries MCP SDK's request_ctx first, then falls back to FastMCP's HTTP context.
### `get_http_headers`
```python
-get_http_headers(include_all: bool = False) -> dict[str, str]
+get_http_headers(include_all: bool = False, include: set[str] | None = None) -> dict[str, str]
```
@@ -165,11 +165,16 @@ Extract headers from the current HTTP request if available.
Never raises an exception, even if there is no active HTTP request (in which case
an empty dict is returned).
-By default, strips problematic headers like `content-length` that cause issues
-if forwarded to downstream clients. If `include_all` is True, all headers are returned.
+By default, strips problematic headers like `content-length` and `authorization`
+that cause issues if forwarded to downstream services. If `include_all` is True,
+all headers are returned.
+
+The `include` parameter allows specific headers to be included even if they would
+normally be excluded. This is useful for proxy transports that need to forward
+authorization headers to upstream MCP servers.
-### `get_access_token`
+### `get_access_token`
```python
get_access_token() -> AccessToken | None
@@ -188,7 +193,7 @@ token snapshot stored in Redis at task submission time.
- The access token if an authenticated user is available, None otherwise.
-### `without_injected_parameters`
+### `without_injected_parameters`
```python
without_injected_parameters(fn: Callable[..., Any]) -> Callable[..., Any]
@@ -213,7 +218,7 @@ Handles:
- Async wrapper function without injected parameters
-### `resolve_dependencies`
+### `resolve_dependencies`
```python
resolve_dependencies(fn: Callable[..., Any], arguments: dict[str, Any]) -> AsyncGenerator[dict[str, Any], None]
@@ -239,7 +244,7 @@ time, so all injection goes through the unified DI system.
which will be filtered out)
-### `CurrentContext`
+### `CurrentContext`
```python
CurrentContext() -> Context
@@ -258,7 +263,7 @@ current MCP operation (tool/resource/prompt call).
- `RuntimeError`: If no active context found (during resolution)
-### `CurrentDocket`
+### `CurrentDocket`
```python
CurrentDocket() -> Docket
@@ -278,7 +283,7 @@ automatically creates for background task scheduling.
- `ImportError`: If fastmcp[tasks] not installed
-### `CurrentWorker`
+### `CurrentWorker`
```python
CurrentWorker() -> Worker
@@ -298,7 +303,7 @@ automatically creates for background task processing.
- `ImportError`: If fastmcp[tasks] not installed
-### `CurrentFastMCP`
+### `CurrentFastMCP`
```python
CurrentFastMCP() -> FastMCP
@@ -316,7 +321,7 @@ This dependency provides access to the active FastMCP server.
- `RuntimeError`: If no server in context (during resolution)
-### `CurrentRequest`
+### `CurrentRequest`
```python
CurrentRequest() -> Request
@@ -336,7 +341,7 @@ current HTTP request. Only available when running over HTTP transports
- `RuntimeError`: If no HTTP request in context (e.g., STDIO transport)
-### `CurrentHeaders`
+### `CurrentHeaders`
```python
CurrentHeaders() -> dict[str, str]
@@ -345,15 +350,16 @@ CurrentHeaders() -> dict[str, str]
Get the current HTTP request headers.
-This dependency provides access to the HTTP headers for the current request.
-Returns an empty dictionary when no HTTP request is available, making it
-safe to use in code that might run over any transport.
+This dependency provides access to the HTTP headers for the current request,
+including the authorization header. Returns an empty dictionary when no HTTP
+request is available, making it safe to use in code that might run over any
+transport.
**Returns:**
- A dependency that resolves to a dictionary of header name -> value
-### `CurrentAccessToken`
+### `CurrentAccessToken`
```python
CurrentAccessToken() -> AccessToken
@@ -372,7 +378,7 @@ authenticated request. Raises an error if no authentication is present.
- `RuntimeError`: If no authenticated user (use get_access_token() for optional)
-### `TokenClaim`
+### `TokenClaim`
```python
TokenClaim(name: str) -> str
@@ -406,7 +412,7 @@ Returned by ``get_task_context()`` when running inside a Docket worker.
Contains identifiers needed to communicate with the MCP session.
-### `ProgressLike`
+### `ProgressLike`
Protocol for progress tracking interface.
@@ -417,7 +423,7 @@ and Docket's Progress (worker context).
**Methods:**
-#### `current`
+#### `current`
```python
current(self) -> int | None
@@ -426,7 +432,7 @@ current(self) -> int | None
Current progress value.
-#### `total`
+#### `total`
```python
total(self) -> int
@@ -435,7 +441,7 @@ total(self) -> int
Total/target progress value.
-#### `message`
+#### `message`
```python
message(self) -> str | None
@@ -444,7 +450,7 @@ message(self) -> str | None
Current progress message.
-#### `set_total`
+#### `set_total`
```python
set_total(self, total: int) -> None
@@ -453,7 +459,7 @@ set_total(self, total: int) -> None
Set the total/target value for progress tracking.
-#### `increment`
+#### `increment`
```python
increment(self, amount: int = 1) -> None
@@ -462,7 +468,7 @@ increment(self, amount: int = 1) -> None
Atomically increment the current progress value.
-#### `set_message`
+#### `set_message`
```python
set_message(self, message: str | None) -> None
@@ -471,7 +477,7 @@ set_message(self, message: str | None) -> None
Update the progress status message.
-### `InMemoryProgress`
+### `InMemoryProgress`
In-memory progress tracker for immediate tool execution.
@@ -483,25 +489,25 @@ progress doesn't need to be observable across processes.
**Methods:**
-#### `current`
+#### `current`
```python
current(self) -> int | None
```
-#### `total`
+#### `total`
```python
total(self) -> int
```
-#### `message`
+#### `message`
```python
message(self) -> str | None
```
-#### `set_total`
+#### `set_total`
```python
set_total(self, total: int) -> None
@@ -510,7 +516,7 @@ set_total(self, total: int) -> None
Set the total/target value for progress tracking.
-#### `increment`
+#### `increment`
```python
increment(self, amount: int = 1) -> None
@@ -519,7 +525,7 @@ increment(self, amount: int = 1) -> None
Atomically increment the current progress value.
-#### `set_message`
+#### `set_message`
```python
set_message(self, message: str | None) -> None
@@ -528,7 +534,7 @@ set_message(self, message: str | None) -> None
Update the progress status message.
-### `Progress`
+### `Progress`
FastMCP Progress dependency that works in both server and worker contexts.
diff --git a/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-resources.mdx b/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-resources.mdx
index 70c67d91c..78331bde6 100644
--- a/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-resources.mdx
+++ b/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-resources.mdx
@@ -14,7 +14,7 @@ and template registration functionality to LocalProvider.
## Classes
-### `ResourceDecoratorMixin`
+### `ResourceDecoratorMixin`
Mixin class providing resource decorator functionality for LocalProvider.
@@ -27,7 +27,7 @@ This mixin contains all methods related to:
**Methods:**
-#### `add_resource`
+#### `add_resource`
```python
add_resource(self: LocalProvider, resource: Resource | ResourceTemplate | Callable[..., Any]) -> Resource | ResourceTemplate
@@ -38,7 +38,7 @@ Add a resource to this provider's storage.
Accepts either a Resource/ResourceTemplate object or a decorated function with __fastmcp__ metadata.
-#### `add_template`
+#### `add_template`
```python
add_template(self: LocalProvider, template: ResourceTemplate) -> ResourceTemplate
@@ -47,7 +47,7 @@ add_template(self: LocalProvider, template: ResourceTemplate) -> ResourceTemplat
Add a resource template to this provider's storage.
-#### `resource`
+#### `resource`
```python
resource(self: LocalProvider, uri: str) -> Callable[[F], F]
diff --git a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
index cc87fd9a7..94f0b7b6a 100644
--- a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
+++ b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx
@@ -27,7 +27,7 @@ run(self, arguments: dict[str, Any]) -> ToolResult
Execute the HTTP request using RequestDirector.
-### `OpenAPIResource`
+### `OpenAPIResource`
Resource implementation for OpenAPI endpoints.
@@ -35,7 +35,7 @@ Resource implementation for OpenAPI endpoints.
**Methods:**
-#### `read`
+#### `read`
```python
read(self) -> ResourceResult
@@ -44,7 +44,7 @@ read(self) -> ResourceResult
Fetch the resource data by making an HTTP request.
-### `OpenAPIResourceTemplate`
+### `OpenAPIResourceTemplate`
Resource template implementation for OpenAPI endpoints.
@@ -52,7 +52,7 @@ Resource template implementation for OpenAPI endpoints.
**Methods:**
-#### `create_resource`
+#### `create_resource`
```python
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> Resource
diff --git a/docs/python-sdk/fastmcp-server-tasks-config.mdx b/docs/python-sdk/fastmcp-server-tasks-config.mdx
index 0dc2bf4ba..f43667a4b 100644
--- a/docs/python-sdk/fastmcp-server-tasks-config.mdx
+++ b/docs/python-sdk/fastmcp-server-tasks-config.mdx
@@ -14,7 +14,7 @@ handle task-augmented execution as specified in SEP-1686.
## Classes
-### `TaskMeta`
+### `TaskMeta`
Metadata for task-augmented execution requests.
@@ -27,7 +27,7 @@ the operation should be submitted as a background task.
- `fn_key`: Docket routing key. Auto-derived from component name if None.
-### `TaskConfig`
+### `TaskConfig`
Configuration for MCP background task execution (SEP-1686).
@@ -44,7 +44,7 @@ Controls how a component handles task-augmented requests:
**Methods:**
-#### `from_bool`
+#### `from_bool`
```python
from_bool(cls, value: bool) -> TaskConfig
@@ -59,7 +59,7 @@ Convert boolean task flag to TaskConfig.
- TaskConfig with appropriate mode.
-#### `supports_tasks`
+#### `supports_tasks`
```python
supports_tasks(self) -> bool
@@ -71,7 +71,7 @@ Check if this component supports task execution.
- True if mode is "optional" or "required", False if "forbidden".
-#### `validate_function`
+#### `validate_function`
```python
validate_function(self, fn: Callable[..., Any], name: str) -> None
diff --git a/docs/python-sdk/fastmcp-tools-function_parsing.mdx b/docs/python-sdk/fastmcp-tools-function_parsing.mdx
index 284b6cdbe..c5aeaf005 100644
--- a/docs/python-sdk/fastmcp-tools-function_parsing.mdx
+++ b/docs/python-sdk/fastmcp-tools-function_parsing.mdx
@@ -10,11 +10,11 @@ Function introspection and schema generation for FastMCP tools.
## Classes
-### `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-function_tool.mdx b/docs/python-sdk/fastmcp-tools-function_tool.mdx
index 97d0d967f..31783bad8 100644
--- a/docs/python-sdk/fastmcp-tools-function_tool.mdx
+++ b/docs/python-sdk/fastmcp-tools-function_tool.mdx
@@ -10,7 +10,7 @@ Standalone @tool decorator for FastMCP.
## Functions
-### `tool`
+### `tool`
```python
tool(name_or_fn: str | Callable[..., Any] | None = None) -> Any
@@ -25,23 +25,23 @@ using mcp.add_tool().
## Classes
-### `DecoratedTool`
+### `DecoratedTool`
Protocol for functions decorated with @tool.
-### `ToolMeta`
+### `ToolMeta`
Metadata attached to functions by the @tool decorator.
-### `FunctionTool`
+### `FunctionTool`
**Methods:**
-#### `to_mcp_tool`
+#### `to_mcp_tool`
```python
to_mcp_tool(self, **overrides: Any) -> mcp.types.Tool
@@ -52,7 +52,7 @@ Convert the FastMCP tool to an MCP tool.
Extends the base implementation to add task execution mode if enabled.
-#### `from_function`
+#### `from_function`
```python
from_function(cls, fn: Callable[..., Any]) -> FunctionTool
@@ -68,7 +68,7 @@ individual parameters must not be passed.
Cannot be used together with metadata parameter.
-#### `run`
+#### `run`
```python
run(self, arguments: dict[str, Any]) -> ToolResult
@@ -77,7 +77,7 @@ run(self, arguments: dict[str, Any]) -> ToolResult
Run the tool with arguments.
-#### `register_with_docket`
+#### `register_with_docket`
```python
register_with_docket(self, docket: Docket) -> None
@@ -89,7 +89,7 @@ FunctionTool registers the underlying function, which has the user's
Depends parameters for docket to resolve.
-#### `add_to_docket`
+#### `add_to_docket`
```python
add_to_docket(self, docket: Docket, arguments: dict[str, Any], **kwargs: Any) -> Execution
diff --git a/src/fastmcp/prompts/function_prompt.py b/src/fastmcp/prompts/function_prompt.py
index a58700a01..d5d2ebef5 100644
--- a/src/fastmcp/prompts/function_prompt.py
+++ b/src/fastmcp/prompts/function_prompt.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import functools
import inspect
import json
import warnings
@@ -160,8 +161,16 @@ class FunctionPrompt(Prompt):
task_config = task_value
task_config.validate_function(fn, func_name)
+ # if the fn is a functools.partial, strip __wrapped__ (set by
+ # update_wrapper) so that inspect.signature() and Pydantic see the
+ # partial's own signature with bound args removed, not the original's
+ if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"):
+ fn = functools.partial(fn.func, *fn.args, **fn.keywords)
+
# if the fn is a callable class, we need to get the __call__ method from here out
- if not inspect.isroutine(fn):
+ # functools.partial is not a routine but Pydantic handles it natively,
+ # so we must not unwrap it to __call__ (which yields a method-wrapper)
+ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
@@ -463,7 +472,7 @@ def prompt(
return create_prompt(fn, prompt_name) # type: ignore[return-value]
return attach_metadata(fn, prompt_name)
- if inspect.isroutine(name_or_fn):
+ if inspect.isroutine(name_or_fn) or isinstance(name_or_fn, functools.partial):
return decorator(name_or_fn, name)
elif isinstance(name_or_fn, str):
if name is not None:
diff --git a/src/fastmcp/resources/function_resource.py b/src/fastmcp/resources/function_resource.py
index bf6673552..c722381ff 100644
--- a/src/fastmcp/resources/function_resource.py
+++ b/src/fastmcp/resources/function_resource.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import functools
import inspect
import warnings
from collections.abc import Callable
@@ -169,8 +170,16 @@ class FunctionResource(Resource):
task_config = task_value
task_config.validate_function(fn, func_name)
+ # if the fn is a functools.partial, strip __wrapped__ (set by
+ # update_wrapper) so that inspect.signature() and Pydantic see the
+ # partial's own signature with bound args removed, not the original's
+ if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"):
+ fn = functools.partial(fn.func, *fn.args, **fn.keywords)
+
# if the fn is a callable class, we need to get the __call__ method from here out
- if not inspect.isroutine(fn):
+ # functools.partial is not a routine but Pydantic handles it natively,
+ # so we must not unwrap it to __call__ (which yields a method-wrapper)
+ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
@@ -256,7 +265,7 @@ def resource(
if isinstance(annotations, dict):
annotations = Annotations(**annotations)
- if inspect.isroutine(uri):
+ if inspect.isroutine(uri) or isinstance(uri, functools.partial):
raise TypeError(
"The @resource decorator requires a URI. "
"Use @resource('uri') instead of @resource"
diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py
index c2fb1b622..8b177af3c 100644
--- a/src/fastmcp/resources/template.py
+++ b/src/fastmcp/resources/template.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import functools
import inspect
import re
from collections.abc import Callable
@@ -551,8 +552,16 @@ class FunctionResourceTemplate(ResourceTemplate):
task_config = task
task_config.validate_function(fn, func_name)
+ # if the fn is a functools.partial, strip __wrapped__ (set by
+ # update_wrapper) so that inspect.signature() and Pydantic see the
+ # partial's own signature with bound args removed, not the original's
+ if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"):
+ fn = functools.partial(fn.func, *fn.args, **fn.keywords)
+
# if the fn is a callable class, we need to get the __call__ method from here out
- if not inspect.isroutine(fn):
+ # functools.partial is not a routine but Pydantic handles it natively,
+ # so we must not unwrap it to __call__ (which yields a method-wrapper)
+ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
diff --git a/src/fastmcp/server/providers/local_provider/decorators/prompts.py b/src/fastmcp/server/providers/local_provider/decorators/prompts.py
index d36c7d2f4..28c92987c 100644
--- a/src/fastmcp/server/providers/local_provider/decorators/prompts.py
+++ b/src/fastmcp/server/providers/local_provider/decorators/prompts.py
@@ -228,7 +228,7 @@ class PromptDecoratorMixin:
self.add_prompt(fn)
return fn
- if inspect.isroutine(name_or_fn):
+ if inspect.isroutine(name_or_fn) or isinstance(name_or_fn, partial):
return decorate_and_register(name_or_fn, name)
elif isinstance(name_or_fn, str):
diff --git a/src/fastmcp/server/providers/local_provider/decorators/resources.py b/src/fastmcp/server/providers/local_provider/decorators/resources.py
index 80a3e9a5c..5d0b6865d 100644
--- a/src/fastmcp/server/providers/local_provider/decorators/resources.py
+++ b/src/fastmcp/server/providers/local_provider/decorators/resources.py
@@ -6,6 +6,7 @@ and template registration functionality to LocalProvider.
from __future__ import annotations
+import functools
import inspect
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, TypeVar
@@ -159,7 +160,7 @@ class ResourceDecoratorMixin:
if isinstance(annotations, dict):
annotations = Annotations(**annotations)
- if inspect.isroutine(uri):
+ if inspect.isroutine(uri) or isinstance(uri, functools.partial):
raise TypeError(
"The @resource decorator was used incorrectly. "
"It requires a URI as the first argument. "
diff --git a/src/fastmcp/server/providers/local_provider/decorators/tools.py b/src/fastmcp/server/providers/local_provider/decorators/tools.py
index 93209eb21..c3cf7c8df 100644
--- a/src/fastmcp/server/providers/local_provider/decorators/tools.py
+++ b/src/fastmcp/server/providers/local_provider/decorators/tools.py
@@ -290,7 +290,7 @@ class ToolDecoratorMixin:
tool_obj = self.add_tool(fn)
return fn
- if inspect.isroutine(name_or_fn):
+ if inspect.isroutine(name_or_fn) or isinstance(name_or_fn, partial):
return decorate_and_register(name_or_fn, name)
elif isinstance(name_or_fn, str):
diff --git a/src/fastmcp/server/tasks/config.py b/src/fastmcp/server/tasks/config.py
index 4956a7667..79afea50f 100644
--- a/src/fastmcp/server/tasks/config.py
+++ b/src/fastmcp/server/tasks/config.py
@@ -6,6 +6,7 @@ handle task-augmented execution as specified in SEP-1686.
from __future__ import annotations
+import functools
import inspect
from collections.abc import Callable
from dataclasses import dataclass
@@ -124,7 +125,11 @@ class TaskConfig:
# Unwrap callable classes and staticmethods
fn_to_check = fn
- if not inspect.isroutine(fn) and callable(fn):
+ if (
+ not inspect.isroutine(fn)
+ and not isinstance(fn, functools.partial)
+ and callable(fn)
+ ):
fn_to_check = fn.__call__
if isinstance(fn_to_check, staticmethod):
fn_to_check = fn_to_check.__func__
diff --git a/src/fastmcp/tools/function_parsing.py b/src/fastmcp/tools/function_parsing.py
index d48f6dbe6..e90330e1e 100644
--- a/src/fastmcp/tools/function_parsing.py
+++ b/src/fastmcp/tools/function_parsing.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import functools
import inspect
from collections.abc import Callable
from dataclasses import dataclass
@@ -102,8 +103,16 @@ class ParsedFunction:
fn_name = getattr(fn, "__name__", None) or fn.__class__.__name__
fn_doc = inspect.getdoc(fn)
+ # if the fn is a functools.partial, strip __wrapped__ (set by
+ # update_wrapper) so that inspect.signature() and Pydantic see the
+ # partial's own signature with bound args removed, not the original's
+ if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"):
+ fn = functools.partial(fn.func, *fn.args, **fn.keywords)
+
# if the fn is a callable class, we need to get the __call__ method from here out
- if not inspect.isroutine(fn):
+ # functools.partial is not a routine but Pydantic handles it natively,
+ # so we must not unwrap it to __call__ (which yields a method-wrapper)
+ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
fn = fn.__call__
# if the fn is a staticmethod, we need to work with the underlying function
if isinstance(fn, staticmethod):
diff --git a/src/fastmcp/tools/function_tool.py b/src/fastmcp/tools/function_tool.py
index 6c1a361f6..823098a8f 100644
--- a/src/fastmcp/tools/function_tool.py
+++ b/src/fastmcp/tools/function_tool.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import functools
import inspect
import warnings
from collections.abc import Callable
@@ -452,7 +453,7 @@ def tool(
return create_tool(fn, tool_name) # type: ignore[return-value]
return attach_metadata(fn, tool_name)
- if inspect.isroutine(name_or_fn):
+ if inspect.isroutine(name_or_fn) or isinstance(name_or_fn, functools.partial):
return decorator(name_or_fn, name)
elif isinstance(name_or_fn, str):
if name is not None:
diff --git a/tests/tools/tool/test_partial.py b/tests/tools/tool/test_partial.py
new file mode 100644
index 000000000..328cf5f1b
--- /dev/null
+++ b/tests/tools/tool/test_partial.py
@@ -0,0 +1,130 @@
+"""Tests for functools.partial support as tools.
+
+See https://github.com/PrefectHQ/fastmcp/issues/3266
+"""
+
+import functools
+
+from mcp.types import TextContent
+
+from fastmcp import FastMCP
+from fastmcp.tools.tool import Tool
+
+
+class TestPartialTool:
+ """Test tools created from functools.partial objects."""
+
+ async def test_partial_sync(self):
+ """Test that a sync functools.partial works as a tool."""
+
+ def add(x: int, y: int) -> int:
+ return x + y
+
+ partial_add = functools.partial(add, y=10)
+ functools.update_wrapper(partial_add, add)
+
+ tool = Tool.from_function(partial_add)
+ result = await tool.run({"x": 5})
+ assert result.content == [TextContent(type="text", text="15")]
+
+ async def test_partial_async(self):
+ """Test that an async functools.partial works as a tool."""
+
+ async def multiply(x: int, factor: int) -> int:
+ return x * factor
+
+ partial_mul = functools.partial(multiply, factor=3)
+ functools.update_wrapper(partial_mul, multiply)
+
+ tool = Tool.from_function(partial_mul)
+ result = await tool.run({"x": 7})
+ assert result.content == [TextContent(type="text", text="21")]
+
+ async def test_partial_preserves_name(self):
+ """Test that the tool name comes from the wrapped function."""
+
+ def greet(name: str, greeting: str = "Hello") -> str:
+ """Greet someone."""
+ return f"{greeting}, {name}!"
+
+ partial_greet = functools.partial(greet, greeting="Hi")
+ functools.update_wrapper(partial_greet, greet)
+
+ tool = Tool.from_function(partial_greet)
+ assert tool.name == "greet"
+ assert tool.description == "Greet someone."
+
+ async def test_partial_custom_name(self):
+ """Test that a custom name overrides the partial's wrapped name."""
+
+ def compute(x: int, op: str) -> str:
+ return f"{op}({x})"
+
+ partial_fn = functools.partial(compute, op="square")
+ functools.update_wrapper(partial_fn, compute)
+
+ tool = Tool.from_function(partial_fn, name="square")
+ assert tool.name == "square"
+
+ async def test_partial_schema_shows_bound_args_as_optional(self):
+ """Test that bound arguments appear as optional with default values."""
+
+ def process(a: int, b: str, c: float = 1.0) -> str:
+ return f"{a}-{b}-{c}"
+
+ partial_fn = functools.partial(process, b="fixed")
+ functools.update_wrapper(partial_fn, process)
+
+ tool = Tool.from_function(partial_fn)
+ props = tool.parameters.get("properties", {})
+ required = tool.parameters.get("required", [])
+ assert "a" in props
+ assert "c" in props
+ # b is bound by the partial so it appears as optional with its
+ # bound value as the default
+ assert "b" in props
+ assert props["b"]["default"] == "fixed"
+ assert "b" not in required
+
+ async def test_partial_without_update_wrapper(self):
+ """Test that functools.partial works without update_wrapper."""
+
+ def add(x: int, y: int) -> int:
+ return x + y
+
+ partial_add = functools.partial(add, y=10)
+ # No update_wrapper call — name comes from the partial class
+
+ tool = Tool.from_function(partial_add, name="add_ten")
+ result = await tool.run({"x": 5})
+ assert result.content == [TextContent(type="text", text="15")]
+
+ async def test_partial_with_add_tool(self):
+ """Test registering a functools.partial via mcp.add_tool()."""
+ mcp = FastMCP("test")
+
+ def greet(name: str, greeting: str = "Hello") -> str:
+ return f"{greeting}, {name}!"
+
+ partial_greet = functools.partial(greet, greeting="Hey")
+ functools.update_wrapper(partial_greet, greet)
+
+ mcp.add_tool(partial_greet)
+
+ result = await mcp.call_tool("greet", {"name": "World"})
+ assert result.content == [TextContent(type="text", text="Hey, World!")]
+
+ async def test_partial_with_server_tool_decorator(self):
+ """Test registering a functools.partial via mcp.tool()."""
+ mcp = FastMCP("test")
+
+ def add(x: int, y: int) -> int:
+ return x + y
+
+ partial_add = functools.partial(add, y=100)
+ functools.update_wrapper(partial_add, add)
+
+ mcp.tool(partial_add)
+
+ result = await mcp.call_tool("add", {"x": 5})
+ assert result.content == [TextContent(type="text", text="105")]