小百学AI

Claude Code 官方教程来啦!Model Context Protocol(MCP)--MCP客户端(六)

tool-tutorials2026/4/28 分钟阅读

当 MCP Server 搭建完成后,真正的挑战才刚刚开始——如何让应用程序与服务端高效通信?本文将从客户端架构到资源机制,系统拆解 MCP 协议中最核心的两个客户端能力。

在上一篇文章中,我们完成了 MCP Server 的搭建,定义了工具并成功运行。但一个孤立的服务端毫无意义,它需要客户端来调用。本文聚焦于 MCP 客户端的实现与资源(Resource)机制的运用,帮助你构建完整的 MCP 应用链路。


客户端架构:两层抽象设计

在大多数实际项目中,开发者通常只需要实现客户端或服务端中的一方。我们在本系列中同时构建两端,目的是让你清晰理解它们之间的协作方式。

MCP 客户端由两个核心组件构成:

  • MCP Client:自定义封装类,简化 Session 的调用方式,并统一管理连接的生命周期。
  • Client Session:MCP Python SDK 提供的底层连接对象,负责与服务端的实际通信。

Client Session 涉及连接资源的管理问题——建立连接后必须正确释放,否则会造成资源泄漏。因此,我们将其封装在自定义的 MCP Client 类中,由该类自动处理连接的初始化与清理工作。

设计原则很简单:上层业务代码只与 MCP Client 交互,底层的连接管理细节完全屏蔽。


客户端的核心职责

回顾整个应用的数据流,客户端在两个关键节点发挥作用:

  1. 从 MCP Server 获取可用工具列表,传递给大模型(如 Claude)。
  2. 当大模型决定调用某个工具时,客户端负责将调用请求转发至 MCP Server 并返回结果。

这意味着客户端需要实现两个核心函数:list_tools()call_tool()

获取工具列表

list_tools() 函数的职责是从 MCP Server 拉取所有已注册的工具定义:

async def list_tools(self) -> list[types.Tool]:
    result = await self.session().list_tools()
    return result.tools

实现非常直接——通过 Session 连接调用 SDK 内置的 list_tools() 方法,从返回结果中提取工具列表。每个工具定义包含名称、描述以及输入参数的 JSON Schema,这些信息将直接传递给大模型,供其决策时使用。

执行工具调用

call_tool() 函数负责将大模型的工具调用请求转发至服务端执行:

async def call_tool(
    self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
    return await self.session().call_tool(tool_name, tool_input)

函数接收工具名称和输入参数(均由大模型生成),通过 Session 转发至 MCP Server,服务端执行对应的工具逻辑后返回结果。

验证客户端功能

客户端文件底部内置了测试入口,可直接运行验证连接是否正常:

uv run mcp_client.py

执行后应输出所有已注册工具的定义信息,包括工具描述和输入参数的 Schema。如果输出为空或报错,需要检查 MCP Server 是否正常运行。


资源机制:比工具更高效的数据获取方式

工具(Tool)适用于需要执行操作的场景,而资源(Resource)则专为数据获取而设计。可以将资源理解为 HTTP 服务中的 GET 请求处理器——它们暴露只读数据,供客户端按需拉取。

为什么需要资源机制

以文档引用功能为例:用户在输入时通过 @document_name 引用某份文档,系统需要完成两件事——列出所有可用文档(用于自动补全),以及获取被引用文档的内容(注入提示词)。

如果使用工具来完成这一流程,大模型需要额外发起一次工具调用才能获取文档内容,增加了响应延迟。而资源机制允许客户端在发送提示词之前就将文档内容注入上下文,大模型无需额外操作即可直接使用这些信息。

两种资源类型

MCP 协议定义了两种资源类型,分别适用于不同场景:

直接资源(Direct Resource) 拥有固定的 URI,不接受参数,适合返回静态或聚合数据:

@mcp.resource(
    "docs://documents",
    mime_type="application/json"
)
def list_docs() -> list[str]:
    return list(docs.keys())

模板资源(Templated Resource) 的 URI 中包含参数占位符,SDK 会自动解析参数并传入函数:

@mcp.resource(
    "docs://documents/{doc_id}",
    mime_type="text/plain"
)
def fetch_doc(doc_id: str) -> str:
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    return docs[doc_id]

资源的返回值类型不受限制——字符串、JSON 对象、二进制数据均可。通过 mime_type 参数告知客户端数据格式,SDK 会自动完成序列化,开发者无需手动处理。


客户端读取资源

服务端定义好资源后,客户端需要实现对应的读取逻辑。核心函数 read_resource() 的实现如下:

async def read_resource(self, uri: str) -> Any:
    result = await self.session().read_resource(AnyUrl(uri))
    resource = result.contents[0]

    if isinstance(resource, types.TextResourceContents):
        if resource.mimeType == "application/json":
            return json.loads(resource.text)

    return resource.text

函数通过 URI 向服务端发起资源请求,获取返回结果后根据 MIME 类型进行解析:JSON 格式的内容会被反序列化为 Python 对象,其他类型则以原始文本形式返回。这种设计使得结构化数据和纯文本内容都能被正确处理。


完整流程串联

当客户端的工具调用和资源读取能力都实现后,完整的应用流程如下:

  1. 用户输入问题,可通过 @ 引用文档资源。
  2. 客户端通过 read_resource() 获取文档内容,注入提示词上下文。
  3. 客户端通过 list_tools() 获取工具列表,连同提示词一并发送给大模型。
  4. 大模型分析问题,决定是否需要调用工具。
  5. 如需调用工具,客户端通过 call_tool() 转发请求并返回结果。
  6. 大模型基于所有信息生成最终回答。

运行主程序进行端到端测试:

uv run main.py

尝试提问"What is the contents of the report.pdf document?",观察工具调用的完整链路。

资源提供上下文,工具执行操作——两者配合构成了 MCP 客户端最核心的能力模型。


总结

MCP 客户端的设计遵循清晰的分层原则:底层 Session 负责通信,上层 Client 封装业务接口。工具和资源分别解决了"执行操作"和"获取数据"两类需求,二者在架构上互补,在实践中协同。

理解了客户端的工作机制,你就掌握了 MCP 应用开发中最关键的一环。下一步,可以尝试将这套架构应用到你自己的项目中,为 AI 工作流集成更丰富的外部能力。

分享:

相关文章

小百学AI 公众号二维码

关注公众号获取最新 AI 资讯

每周精选 AI 领域最值得关注的新闻、工具和教程,助你保持技术敏感度。

每周更新独家内容工具推荐