第四章 核心概念--会话(Session)
目录
简介
会话(Session)是MCP(Model Context Protocol)客户端与服务器之间通信的核心机制。它封装了客户端与一个或多个MCP服务器之间的连接状态、消息传输和上下文管理。通过ClientSession类,客户端能够初始化连接、发送请求、接收通知,并管理工具调用、资源读取和提示词获取等交互。ClientSessionGroup类则进一步扩展了这一能力,支持同时管理多个服务器会话,实现工具、资源和提示词的聚合。本文档将深入剖析会话的内部机制,涵盖其生命周期、通信模型、上下文访问以及在复杂场景下的应用。
会话生命周期管理
ClientSession的生命周期始于其初始化,终于连接的关闭。该过程严格遵循MCP协议规范,确保客户端与服务器之间的正确握手和状态同步。
会话初始化 (initialize)
会话的初始化是建立连接后的首要步骤。initialize()方法向服务器发送initialize请求,该请求包含客户端声明的能力(Capabilities)和基本信息(clientInfo)。这些能力包括:
- 采样能力 (Sampling):如果客户端提供了
sampling_callback,则声明支持采样功能。 - 征询能力 (Elicitation):如果客户端提供了
elicitation_callback,则声明支持征询功能。 - 根目录变更通知能力 (Roots):如果客户端提供了
list_roots_callback,则声明支持接收根目录变更通知。
服务器在收到initialize请求后,会返回一个包含其协议版本和服务器信息的响应。客户端会验证服务器的协议版本是否在支持的范围内(SUPPORTED_PROTOCOL_VERSIONS),若不支持则抛出异常。初始化成功后,客户端会立即发送一个initialized通知,标志着初始化阶段的完成,此后会话进入可操作状态。
图表来源
本节来源
请求/通知发送与超时处理
ClientSession通过send_request()和send_notification()方法实现与服务器的通信。send_request()用于发送需要服务器响应的请求(如调用工具、读取资源),而send_notification()用于发送无需响应的通知(如进度更新、日志级别设置)。
所有请求都支持超时控制。在__init__方法中,可以设置read_timeout_seconds作为所有请求的默认读取超时时间。此外,在发送特定请求(如call_tool)时,也可以通过read_timeout_seconds参数为单个请求设置独立的超时时间,这提供了更精细的控制。
进度回调
对于长时间运行的操作,客户端可以通过send_progress_notification()方法向服务器发送进度更新。该方法允许指定进度令牌(progress_token)、当前进度值(progress)、总进度值(total)和可选的消息(message),使服务器能够向用户展示操作的实时进度。
本节来源
消息通信模型
MCP会话的消息通信基于JSON-RPC 2.0协议,并通过SessionMessage结构进行封装,以支持传输层特定的功能。
SessionMessage 结构
SessionMessage是消息通信的核心数据结构,它将一个JSONRPCMessage(包含JSON-RPC请求、响应或通知)与一个可选的metadata字段组合在一起。
图表来源
- message.py
metadata字段的类型为ClientMessageMetadata | ServerMessageMetadata | None,其内容取决于消息的发送方:
- 客户端消息元数据 (
ClientMessageMetadata):包含resumption_token(用于会话恢复的令牌)和on_resumption_token_update回调(用于在令牌更新时通知客户端)。 - 服务器消息元数据 (
ServerMessageMetadata):包含related_request_id(关联的请求ID,用于将通知与特定请求关联)和request_context(请求特定的上下文,如HTTP头、认证信息等)。
这种设计使得底层传输层(如SSE、Streamable HTTP)能够利用元数据来实现会话恢复、请求关联等高级功能,而不会干扰核心的JSON-RPC消息。
内部消息处理流程
当会话从服务器接收到消息时,会通过_handle_incoming()方法进行处理。该方法会将接收到的请求、通知或异常转发给用户在初始化时提供的message_handler。对于服务器发来的请求(如createMessage、elicitation),会话会根据其类型调用相应的回调函数(_sampling_callback, _elicitation_callback等),并将结果通过响应器(responder)返回。对于服务器发来的通知(如loggingMessage),会话会直接调用相应的处理函数(如_logging_callback)。
本节来源
上下文控制能力
ClientSession不仅是一个通信通道,还提供了强大的上下文控制能力,允许客户端在工具调用、资源管理等场景中进行深度交互。
通过 ctx.session 访问底层接口
在服务器端的工具或资源函数中,可以通过Context对象的ctx.session属性访问底层的ClientSession实例。这为服务器端代码提供了直接与客户端通信的能力,例如:
- 推送日志:服务器可以调用
ctx.session.set_logging_level()来动态调整日志级别,或通过ctx.session._logging_callback直接处理日志通知。 - 发送资源变更通知:当服务器端的资源发生变更时,可以通过
ctx.session.send_roots_list_changed()发送roots/list_changed通知,提示客户端刷新资源列表。
工具结果验证
ClientSession内置了对工具调用结果的验证机制。在call_tool()方法中,如果调用成功且结果不是错误,会调用_validate_tool_result()方法。该方法会检查工具的输出模式(output schema):
- 如果工具的输出模式未被缓存,则先调用
list_tools()获取并缓存所有工具的输出模式。 - 如果工具声明了输出模式但返回结果中没有
structuredContent,则抛出异常。 - 使用
jsonschema.validate库对structuredContent进行验证,确保其符合预定义的模式,从而保证数据的完整性和正确性。
本节来源
多服务器聚合场景
ClientSessionGroup类是处理多服务器场景的核心,它能够同时管理多个ClientSession,并将来自不同服务器的工具、资源和提示词聚合到一个统一的接口中。
聚合与连接管理
ClientSessionGroup通过connect_to_server()或connect_with_session()方法连接到服务器。connect_to_server()接受服务器参数(如URL、超时设置),内部会调用相应的客户端(如stdio_client, sse_client)建立连接并初始化会话。connect_with_session()则允许用户传入一个已建立的ClientSession。
连接成功后,_aggregate_components()方法会被调用。该方法会并发地从新连接的服务器上获取其所有工具、资源和提示词,并将它们添加到ClientSessionGroup的聚合字典(_tools, _resources, _prompts)中。同时,它还会建立一个从工具名到其所属ClientSession的映射(_tool_to_session),以便后续的工具调用能路由到正确的会话。
图表来源
命名冲突处理
当多个服务器提供同名的工具、资源或提示词时,ClientSessionGroup会抛出异常。为了解决此问题,它提供了一个component_name_hook钩子函数。用户可以在初始化ClientSessionGroup时提供一个自定义函数,该函数接收组件名和服务器信息,并返回一个新的、唯一的名称。例如,可以将名称修改为{server_name}_{original_name},从而有效避免命名冲突。
本节来源
工具调用与会话断开
call_tool()方法利用_tool_to_session映射找到目标工具所属的ClientSession,然后在该会话上调用call_tool(),实现了对多服务器工具的透明调用。
disconnect_from_server()方法用于安全地断开与特定服务器的连接。它会从聚合字典中移除该服务器提供的所有组件,并清理其关联的资源(如AsyncExitStack),确保资源得到正确释放。
本节来源
典型交互应用模式
会话机制在各种典型交互中扮演着关键角色。
工具调用
通过call_tool()方法,客户端可以调用服务器上注册的任何工具。该方法支持传递参数、设置超时、提供进度回调,并自动验证返回结果的结构化内容。
资源读取
read_resource()方法允许客户端根据URI读取服务器上的资源。list_resources()和list_resource_templates()方法则用于发现可用的资源及其模板。
提示词获取
get_prompt()方法用于根据名称获取一个提示词。list_prompts()方法用于列出所有可用的提示词。complete()方法则用于获取参数的补全建议,支持基于上下文的智能补全。
本节来源
会话池管理与最佳实践
为了高效管理会话,应遵循以下最佳实践:
使用上下文管理器
ClientSession和ClientSessionGroup都实现了异步上下文管理器(__aenter__和__aexit__)。务必使用async with语句来创建和管理会话,以确保在发生异常或正常退出时,连接和相关资源能够被正确关闭。
异常恢复
利用SessionMessage中的resumption_token,可以在连接中断后尝试恢复会话状态,避免从头开始。客户端应实现on_resumption_token_update回调来安全地存储最新的恢复令牌。
并发控制
ClientSessionGroup在断开连接时使用anyio.create_task_group()并发地关闭各个会话的资源栈,这显著提高了资源清理的效率。在高并发场景下,应确保对共享状态的访问是线程安全的。
最佳实践指导
- 及时初始化:在使用会话前,务必调用
initialize()方法。 - 处理超时:为长时间运行的请求设置合理的超时时间,避免无限等待。
- 聚合命名:在使用
ClientSessionGroup时,始终提供component_name_hook以防止命名冲突。 - 资源清理:始终通过上下文管理器或显式调用
aclose()来释放会话资源。
更多推荐



所有评论(0)