目录

  1. 简介
  2. 会话生命周期管理
  3. 消息通信模型
  4. 上下文控制能力
  5. 多服务器聚合场景
  6. 典型交互应用模式
  7. 会话池管理与最佳实践

简介

会话(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通知,标志着初始化阶段的完成,此后会话进入可操作状态。

客户端 服务器 initialize(客户端能力, 客户端信息) InitializeResult(服务器信息, 协议版本) 客户端验证协议版本 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字段组合在一起。

包含
包含
SessionMessage
+message : JSONRPCMessage
+metadata : MessageMetadata
ClientMessageMetadata
+resumption_token : ResumptionToken
+on_resumption_token_update : Callable[[ResumptionToken], Awaitable[None]]
ServerMessageMetadata
+related_request_id : RequestId
+request_context : object
MessageMetadata
JSONRPCMessage

图表来源

  • 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。对于服务器发来的请求(如createMessageelicitation),会话会根据其类型调用相应的回调函数(_sampling_callback, _elicitation_callback等),并将结果通过响应器(responder)返回。对于服务器发来的通知(如loggingMessage),会话会直接调用相应的处理函数(如_logging_callback)。

收到消息
_是请求吗?
调用对应的回调函数
通过responder发送响应
_是通知吗?
调用对应的处理函数
转发给message_handler

本节来源

上下文控制能力

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):

  1. 如果工具的输出模式未被缓存,则先调用list_tools()获取并缓存所有工具的输出模式。
  2. 如果工具声明了输出模式但返回结果中没有structuredContent,则抛出异常。
  3. 使用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()方法则用于获取参数的补全建议,支持基于上下文的智能补全。

本节来源

会话池管理与最佳实践

为了高效管理会话,应遵循以下最佳实践:

使用上下文管理器

ClientSessionClientSessionGroup都实现了异步上下文管理器(__aenter____aexit__)。务必使用async with语句来创建和管理会话,以确保在发生异常或正常退出时,连接和相关资源能够被正确关闭。

异常恢复

利用SessionMessage中的resumption_token,可以在连接中断后尝试恢复会话状态,避免从头开始。客户端应实现on_resumption_token_update回调来安全地存储最新的恢复令牌。

并发控制

ClientSessionGroup在断开连接时使用anyio.create_task_group()并发地关闭各个会话的资源栈,这显著提高了资源清理的效率。在高并发场景下,应确保对共享状态的访问是线程安全的。

最佳实践指导

  1. 及时初始化:在使用会话前,务必调用initialize()方法。
  2. 处理超时:为长时间运行的请求设置合理的超时时间,避免无限等待。
  3. 聚合命名:在使用ClientSessionGroup时,始终提供component_name_hook以防止命名冲突。
  4. 资源清理:始终通过上下文管理器或显式调用aclose()来释放会话资源。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐