在构建复杂的AI应用时,合理管理应用的生命周期和处理异步上下文是至关重要的。MCP框架提供了强大的异步生命周期管理和上下文处理机制,使开发者能够优雅地处理资源初始化、释放和异步通信。本文将深入探讨MCP的异步生命周期管理和上下文处理机制,介绍如何使用异步上下文管理器定义应用生命周期,如何在客户端处理服务器日志,以及如何构建具有完整生命周期管理的AI应用。通过这些示例,你将掌握构建健壮、可维护的AI服务的关键技术。
异步生命周期管理简介
应用生命周期管理是指控制应用启动和关闭过程中的资源初始化和清理。在异步环境中,这些操作通常需要使用异步上下文管理器来实现。MCP框架提供了优雅的方式来定义和管理应用的生命周期,确保资源在应用启动时正确初始化,在应用关闭时妥善释放。
服务器端生命周期管理
定义应用生命周期
使用asynccontextmanager装饰器,我们可以创建一个异步上下文管理器来定义应用的生命周期:
@asynccontextmanager
async def app_lifespan(app: FastMCP) -> AsyncIterator[None]:
"""管理应用程序生命周期"""
logger.info("应用程序启动...")
try:
# 执行启动操作,如数据库连接、缓存初始化等
yield
finally:
# 执行关闭操作,如关闭连接、释放资源等
logger.info("应用程序关闭...")
这个生命周期管理器在应用启动时执行yield之前的代码,在应用关闭时执行yield之后的代码。通过使用try-finally结构,我们可以确保即使在出现异常的情况下,清理代码也能正确执行。
将生命周期管理器应用到MCP实例
创建MCP实例时,我们可以通过lifespan参数指定生命周期管理器:
mcp = FastMCP(
name="context_server",
lifespan=app_lifespan,
debug=True,
log_level="DEBUG"
)
这样,MCP框架会在服务器启动和关闭时调用相应的生命周期方法。
在工具中使用上下文
MCP工具可以接收Context对象,用于与客户端进行实时通信:
@mcp.tool()
async def query_db(ctx: Context) -> str:
"""使用初始化资源的工具"""
logger.debug('query_db 开始执行')
await ctx.info('开始查询数据库')
# 模拟数据库操作
await asyncio.sleep(1)
await ctx.info('数据库查询完成')
logger.debug('query_db 执行完成')
return "query_db 执行成功"
通过ctx对象,工具可以向客户端发送不同级别的日志消息,报告进度,或者访问资源。
客户端上下文处理
处理服务器日志
在客户端,我们可以注册日志处理器来接收和处理服务器发送的日志消息:
# 设置日志处理器
async def handle_log(level: str, message: str):
logger.info(f"服务器日志 [{level}]: {message}")
# 注册日志处理器
session.on_log = handle_log
这样,当服务器通过ctx.info()等方法发送日志时,客户端的handle_log函数会被调用来处理这些日志。
客户端会话管理
客户端使用异步上下文管理器来管理与服务器的连接:
async with stdio_client(server_params) as stream:
async with ClientSession(*stream) as session:
# 初始化连接
await session.initialize()
# 注册日志处理器
session.on_log = handle_log
# 调用工具
result = await session.call_tool("query_db", {})
print(f"工具调用结果: {result}")
这种方式确保了连接在使用完毕后被正确关闭,资源得到适当释放。
完整示例解析
服务器端(10_async_context_server.py)
服务器端定义了应用生命周期和一个使用上下文的工具:
@asynccontextmanager
async def app_lifespan(app: FastMCP) -> AsyncIterator[None]:
logger.info("应用程序启动...")
try:
yield
finally:
logger.info("应用程序关闭...")
mcp = FastMCP(
name="context_server",
lifespan=app_lifespan,
debug=True,
log_level="DEBUG"
)
@mcp.tool()
async def query_db(ctx: Context) -> str:
await ctx.info('开始查询数据库')
await asyncio.sleep(1)
await ctx.info('数据库查询完成')
return "query_db 执行成功"
客户端(10_async_context_client.py)
客户端连接到服务器,注册日志处理器,并调用工具:
async def test_async_context_examples():
server_params = StdioServerParameters(
command="python",
args=["mcp/10_async_context_server.py"],
env=None
)
async with stdio_client(server_params) as stream:
async with ClientSession(*stream) as session:
await session.initialize()
async def handle_log(level: str, message: str):
logger.info(f"服务器日志 [{level}]: {message}")
session.on_log = handle_log
result = await session.call_tool("query_db", {})
print(f"工具调用结果: {result}")
集成示例(10_async_context_examples.py)
这个示例将服务器和客户端集成在同一个文件中,展示了完整的工作流程:
@asynccontextmanager
async def app_lifespan(app: FastMCP) -> AsyncIterator[None]:
print("lifespan start")
yield
print("lifespan end")
mcp = FastMCP("My App", lifespan=app_lifespan, debug=True, log_level="DEBUG")
@mcp.tool()
async def query_db(ctx: Context) -> str:
await ctx.debug('query_db start')
await asyncio.sleep(1)
await ctx.debug('query_db end')
return "query_db"
async def test_async_context_examples():
server_params = StdioServerParameters(
command="python",
args=["mcp/10_async_context_examples.py", "--server"],
env=None
)
async with stdio_client(server_params) as stream:
async with ClientSession(*stream) as session:
await session.initialize()
result = await session.call_tool("query_db", {})
print(result)
异步生命周期的应用场景
异步生命周期管理和上下文处理适用于多种场景:
- 数据库连接管理:在应用启动时初始化数据库连接池,在关闭时释放连接
- 缓存系统初始化:预热缓存或建立缓存连接
- 外部服务集成:初始化与外部API的连接
- 资源预加载:预加载模型、配置或其他资源
- 临时文件管理:创建和清理临时文件目录
- 定时任务调度:启动和停止定时任务
- 分布式锁管理:获取和释放分布式锁
最佳实践
服务器端
- 明确的资源管理:在生命周期管理器中清晰地定义资源的初始化和释放
- 异常处理:使用
try-finally确保资源在异常情况下也能正确释放 - 日志记录:记录生命周期事件,便于调试和监控
- 优雅关闭:实现优雅关闭机制,确保请求处理完成后再关闭资源
- 依赖注入:通过生命周期上下文向应用提供依赖
客户端
- 连接管理:使用异步上下文管理器管理连接
- 错误处理:处理连接错误和服务器异常
- 日志处理:注册日志处理器接收服务器日志
- 资源释放:确保在完成操作后释放资源
- 重试机制:实现连接重试机制,提高可靠性
异步上下文与生命周期的优势
- 资源安全:确保资源在不再需要时被正确释放
- 代码组织:提供清晰的结构来组织初始化和清理代码
- 错误处理:提供统一的错误处理机制
- 可测试性:使应用更容易测试,因为资源生命周期明确定义
- 可维护性:提高代码的可维护性和可读性
- 实时通信:支持服务器和客户端之间的实时通信