PrefectHQ/fastmcp · error · ValueError

'{name}' uses a sync function but has task execution enabled

Error message

'{name}' uses a sync function but has task execution enabled. Background tasks require async functions.

What it means

FastMCP raises this ValueError when a component (tool, resource, prompt) is configured with task execution enabled but the underlying callable is a synchronous function. Background task execution is implemented with asyncio, so only coroutine functions can be scheduled to run in the background. The check runs during component construction via from_function, failing fast rather than at task submission time.

Source

Thrown at fastmcp_slim/fastmcp/utilities/tasks.py:80

        return self.mode != "forbidden"

    def validate_function(self, fn: Callable[..., Any], name: str) -> None:
        """Validate that a function is compatible with this task config."""
        if not self.supports_tasks():
            return

        fn_to_check = 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__

        if not is_coroutine_function(fn_to_check):
            raise ValueError(
                f"'{name}' uses a sync function but has task execution enabled. "
                "Background tasks require async functions."
            )

View on GitHub (pinned to 1f02114297)

Solutions

  1. Convert the function to an async def function (awaiting async I/O or wrapping blocking work with asyncio.to_thread)
  2. Keep the function sync but disable task execution for the component (remove the task=True / task execution config)
  3. If blocking work must stay sync but be non-blocking, create an async wrapper that calls asyncio.to_thread(sync_fn, ...) and register the wrapper with task execution enabled
  4. If using a callable object or staticmethod, ensure the __call__/__func__ actually is a coroutine function, since the check unwraps those

Example fix

// before
@mcp.tool(task=True)
def fetch_data(url: str) -> str:
    return requests.get(url).text

// after
@mcp.tool(task=True)
async def fetch_data(url: str) -> str:
    return await asyncio.to_thread(requests.get, url).text
Defensive patterns

Strategy: validation

Validate before calling

import asyncio
if not asyncio.iscoroutinefunction(fn):
    raise TypeError(f"{fn.__name__} must be async def when task execution is enabled")

Type guard

def is_async_fn(fn) -> bool:
    import asyncio
    target = fn.__func__ if isinstance(fn, staticmethod) else getattr(fn, "__call__", fn)
    return asyncio.iscoroutinefunction(target)

Prevention

When it happens

Trigger: Calling Tool.from_function / Resource.from_function / Prompt.from_function (or decorators like @mcp.tool) on a plain `def` function while passing task execution enabled (e.g. task=True or an execution/task config). The check inspects fn (or fn.__call__ / staticmethod __func__) with is_coroutine_function and raises when it returns False.

Common situations: Developers write a sync helper (doing blocking I/O or CPU work) and enable background task mode so it runs 'async', not realizing async scheduling requires a coroutine function. Also common after refactoring an async function to sync while leaving task config in place, or wrapping functions where the wrapper is sync.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/95ec4376bbabb1a7. Report an issue: GitHub.