FastMCP 高级特性之Background Tasks

Background Tasks

以异步的方式运行长耗时的操作,并跟踪进度。

FastMCP 实现了 MCP 后台任务协议(SEP-1686),只需修改一个装饰器,就能为你的服务器提供一个可用于生产环境的分布式任务调度器。

什么是 Docket?

FastMCP 的任务系统由Docket提供支持,它最初由Prefect开发,用于为Prefect Cloud的托管任务调度和执行服务提供动力,该服务每天处理数百万个并发任务。如今,Docket 已开源给社区。

什么是 MCP 后台任务?

在 MCP 中,所有组件交互默认都是阻塞式的。当客户端调用工具、读取资源或获取提示时,它会发送请求并等待响应。对于需要数秒或数分钟的操作,这会带来糟糕的用户体验。

MCP 后台任务协议通过允许客户端执行以下操作解决了这个问题:

  1. 启动一个操作并立即收到一个任务 ID
  2. 跟踪操作运行时的进度
  3. 在结果就绪时检索结果

FastMCP 会为你处理所有这些事情。在你的装饰器中添加task=True,你的函数就会获得完整的后台执行能力,包括进度报告、分布式处理和水平扩展。

MCP 后台任务与 Python 并发

简单说就是:在你的 FastMCP 服务器里,想怎么处理"并行/异步任务"都可以------要么用 Python 自带的工具(比如 asyncio 异步、线程、多进程),要么用外面的任务队列工具;

反正 FastMCP 本质就是 Python 写的,你习惯怎么写 Python 代码,就怎么用它跑任务,没有限制。 核心是强调灵活性:不强制你用 FastMCP 自己的任务功能,而是支持你用熟悉的 Python 原生方案或其他工具,怎么方便怎么来。

启用后台任务

在任何工具、资源、资源模板或提示装饰器中添加task=True。这会将该组件标记为能够进行后台执行。

python 复制代码
import asyncio
from fastmcp import FastMCP

mcp = FastMCP("MyServer")

@mcp.tool(task=True)
async def slow_computation(duration: int) -> str:
    """A long-running operation."""
    for i in range(duration):
        await asyncio.sleep(1)
    return f"Completed in {duration} seconds"
  1. 客户端发请求时:只要客户端说 "我要让这个任务在后台跑",服务器会立刻返回一个 "任务 ID"(相当于给这个任务发了个唯一编号),不用等任务做完。
  2. 任务实际执行时:真正的工作是交给 "后台工作节点"(专门处理耗时任务的 "小助手")来跑的,不占用客户端和服务器的实时交互资源。
  3. 客户端查结果时:有两种方式 ------ 要么主动用之前拿到的 "任务 ID" 去问服务器 "任务进度咋样了"(也就是 "轮询状态"),要么等着服务器主动把结果推过来 / 等结果生成后再取("等待结果")。

后台任务需要异步函数。尝试将task=True与同步函数一起使用会在注册时引发ValueError。

**客户端代码**

python 复制代码
import asyncio
from fastmcp import FastMCPClient

async def main():
    # 1. 初始化客户端,连接到 FastMCP 服务器
    client = FastMCPClient(
        server_address="http://localhost:8000",  # 替换成你的服务器地址
        server_name="MyServer"  # 要和服务端的 FastMCP("MyServer") 一致
    )

    try:
        # 2. 调用带 task=True 的工具,触发后台任务,立即返回任务 ID
        # 注意:调用时需要指定 task 相关参数(匹配服务端的 task 模式)
        task_response = await client.call_tool(
            tool_name="slow_computation",
            arguments={"duration": 5},  # 传入耗时参数,比如 5 秒
            task={"enabled": True}  # 开启任务模式,获取任务 ID
        )

        # 3. 提取任务 ID
        task_id = task_response.task_id
        print(f"后台任务已启动,任务 ID: {task_id}")

        # 4. 轮询任务状态,直到完成
        while True:
            # 查询任务状态
            task_status = await client.get_task_status(task_id)
            print(f"当前任务状态: {task_status.status}")

            if task_status.status == "completed":
                # 任务完成,获取结果
                print(f"任务执行成功!结果: {task_status.result}")
                break
            elif task_status.status == "failed":
                # 任务失败,打印错误信息
                print(f"任务执行失败!错误: {task_status.error}")
                break
            
            # 每隔 1 秒查询一次
            await asyncio.sleep(1)

    finally:
        # 关闭客户端连接
        await client.close()

# 运行客户端主函数
if __name__ == "__main__":
    asyncio.run(main())

Execution Modes:

为了对任务执行行为进行细粒度控制,请使用TaskConfig而非布尔值简写形式。MCP 任务协议定义了三种执行模式:

|-----------|---------------------------|-----------------------------|
| Mode | Client calls without task | Client calls with task |
| forbidden | Executes synchronously | Error: task not supported |
| optional | Executes synchronously | Executes as background task |
| required | Error: task required | Executes as background task |

这张表是用来定义 客户端调用接口时,"是否携带任务(Task)" 与 "接口执行方式" 的对应规则,核心是通过 3 种 "Mode(模式)" 明确不同场景下的行为,避免混乱:

  1. "forbidden"(禁止任务模式)

    • 客户端不带任务调用:正常同步执行(按传统方式,等结果返回);
    • 客户端带任务调用:直接报错(因为这种模式明确不支持 "任务",带了就违规)。
  2. "optional"(任务可选模式)

    • 客户端不带任务调用:同步执行(兼容传统用法);
    • 客户端带任务调用:异步后台执行(有任务就用后台模式,灵活适配两种需求)。
  3. "required"(任务必选模式)

    • 客户端不带任务调用:直接报错(必须带任务才能用,强制走任务流程);
    • 客户端带任务调用:异步后台执行(符合该模式的核心要求)。
python 复制代码
from fastmcp import FastMCP
from fastmcp.server.tasks import TaskConfig

mcp = FastMCP("MyServer")

# Supports both sync and background execution (default when task=True)
@mcp.tool(task=TaskConfig(mode="optional"))
async def flexible_task() -> str:
    return "Works either way"

# Requires background execution - errors if client doesn't request task
@mcp.tool(task=TaskConfig(mode="required"))
async def must_be_background() -> str:
    return "Only runs as a background task"

# No task support (default when task=False or omitted)
@mcp.tool(task=TaskConfig(mode="forbidden"))
async def sync_only() -> str:
    return "Never runs as background task"

布尔值快捷方式对应以下模式:

task=True → TaskConfig(mode="optional")

task=False → TaskConfig(mode="forbidden")

轮询间隔

当客户端轮询任务状态时,服务器会告知它们应多久回来检查一次。默认情况下,FastMCP 建议 5 秒的间隔,但你可以按组件自定义此间隔:

python 复制代码
from datetime import timedelta
from fastmcp import FastMCP
from fastmcp.server.tasks import TaskConfig

mcp = FastMCP("MyServer")

# Poll every 2 seconds for a fast-completing task
@mcp.tool(task=TaskConfig(mode="optional", poll_interval=timedelta(seconds=2)))
async def quick_task() -> str:
    return "Done quickly"

# Poll every 30 seconds for a long-running task
@mcp.tool(task=TaskConfig(mode="optional", poll_interval=timedelta(seconds=30)))
async def slow_task() -> str:
    return "Eventually done"

较短的间隔能为客户端提供更快的反馈,但会增加服务器负载。较长的间隔会减少负载,但会延迟状态更新。

服务器范围的默认设置

要默认启用所有组件的后台任务支持,请在构造函数中传入tasks=True。各个装饰器仍然可以通过task=False来覆盖此设置。

python 复制代码
mcp = FastMCP("MyServer", tasks=True)

如果你的服务器定义了任何同步工具、资源或提示,你需要在它们的装饰器上显式设置task=False以避免错误。

优雅降级

当客户端请求后台执行但组件具有mode="forbidden"时,FastMCP 会同步执行并内联返回结果。这遵循 SEP-1686 规范中的优雅降级原则 ------ 客户端始终可以请求后台执行,而无需担心服务器的能力。

相反,当组件具有mode="required"但客户端未请求后台执行时,FastMCP 会返回一个错误,表明任务执行是必需的。

Configuration

    • 环境变量

FASTMCP_DOCKET_URL

    • 默认值

memory://

    • 描述

Backend URL (memory:// or redis://host:port/db)

Backends

FastMCP 支持两种用于任务执行的后端,每种后端都有不同的权衡。

In-Memory Backend (Default)

内存后端(memory://)无需任何配置,开箱即可使用。
优势:

  • 无外部依赖
  • 简单的单进程部署

劣势:

  • 临时性:如果服务器重启,所有未完成的任务都会丢失
  • 延迟较高:约 250 毫秒的任务获取时间,而 Redis 则为个位数毫秒
  • 不支持水平扩展:仅支持单进程 ------ 无法添加额外的工作进程

Redis Backend

对于生产环境部署,请通过设置FASTMCP_DOCKET_URL=redis://localhost:6379,将 Redis(或 Valkey)用作后端。

优势:

  • 持久性:任务在服务器重启后仍然存在
  • 快速性:任务获取延迟为个位数毫秒
  • 可扩展性:可添加工作节点以在进程或机器间分配负载

Workers

每个具有任务启用组件的 FastMCP 服务器都会自动启动一个嵌入式工作器。你无需启动单独的工作器进程来执行任务。

要进行水平扩展,请使用命令行界面添加更多工作器:

python 复制代码
fastmcp tasks worker server.py

每个额外的工作进程都会从同一个队列中获取任务,从而在多个进程间分配负载。通过环境配置工作进程的并发数:

python 复制代码
export FASTMCP_DOCKET_CONCURRENCY=20
fastmcp tasks worker server.py

额外的工作进程仅适用于 Redis/Valkey 后端。内存后端仅支持单进程。

Progress Reporting

Progress依赖项允许你向客户端报告进度。将其作为带有默认值的参数注入,FastMCP 会提供活动的进度报告器。

python 复制代码
from fastmcp import FastMCP
from fastmcp.dependencies import Progress

mcp = FastMCP("MyServer")

@mcp.tool(task=True)
async def process_files(files: list[str], progress: Progress = Progress()) -> str:
    await progress.set_total(len(files))

    for file in files:
        await progress.set_message(f"Processing {file}")
        # ... do work ...
        await progress.increment()

    return f"Processed {len(files)} files"

进度 API:

  • await progress.set_total(n) --- 设置总步骤数
  • await progress.increment(amount=1) --- 增加进度
  • await progress.set_message(text) --- 更新状态消息

进度在即时执行模式和后台执行模式下均能运行 ------ 无论客户端如何调用你的函数,你都可以使用相同的代码。

Docket Dependencies

FastMCP 在您启用任务的函数中暴露了 Docket 的完整依赖注入系统。除了Progress之外,您还可以访问 Docket 实例、工作器信息,并使用诸如重试和超时等高级功能。

python 复制代码
from docket import Docket, Worker
from fastmcp import FastMCP
from fastmcp.dependencies import Progress, CurrentDocket, CurrentWorker

mcp = FastMCP("MyServer")

@mcp.tool(task=True)
async def my_task(
    progress: Progress = Progress(),
    docket: Docket = CurrentDocket(),
    worker: Worker = CurrentWorker(),
) -> str:
    # Schedule additional background work
    await docket.add(another_task, arg1, arg2)

    # Access worker metadata
    worker_name = worker.name

    return "Done"

借助CurrentDocket(),您可以安排额外的后台任务、将工作串联起来以及协调复杂的工作流。有关完整的 API(包括重试策略、超时设置和自定义依赖项),请参阅Docket 文档。

相关推荐
我是华为OD~HR~栗栗呀2 小时前
(华为od)21届-Python面经
java·前端·c++·python·华为od·华为·面试
rayufo2 小时前
arXiv论文《Content-Aware Transformer for All-in-one Image Restoration》解读与代码实现
人工智能·深度学习·transformer
Jerryhut2 小时前
Opencv总结1——视频读取与处理,图像阈值和平滑处理,图像形态学操作
人工智能·opencv·计算机视觉
艾醒(AiXing-w)2 小时前
大模型原理剖析——拆解预训练、微调、奖励建模与强化学习四阶段(以ChatGPT构建流程为例)
人工智能·chatgpt
币圈菜头2 小时前
GAEA Carbon-Silicon Symbiotism NFT 解析:它在系统中扮演的角色,以及与空投权重的关系
人工智能·web3·去中心化·区块链
Deepoch2 小时前
从“飞行相机”到“空中智能体”:无人机如何重构行业生产力
人工智能·科技·机器人·无人机·开发板·具身模型·deepoc
OAK中国_官方2 小时前
OAK HUB:您通往视觉AI的门户!
人工智能·计算机视觉·depthai
鲨莎分不晴2 小时前
独立学习 (IQL):大道至简还是掩耳盗铃
人工智能·深度学习·学习
audyxiao0012 小时前
如何用Gemini“上车”自动驾驶?通过视觉问答完成自动驾驶任务
人工智能·机器学习·自动驾驶·大语言模型·多模态·gemini