现在我们的 MCP 服务器已经正常工作,接下来是时候构建客户端了。客户端是让我们的应用程序能够与 MCP 服务器通信并访问其功能的部分。
理解客户端架构
在大多数实际项目中,您通常只会实现一个 MCP 客户端或一个 MCP 服务器——而不是两者都实现。我们在这个项目中同时构建两者,只是为了让您看到它们如何协同工作。

MCP 客户端由两个主要组件组成:
- MCP Client - 我们创建的一个自定义类,用于简化会话的使用
- Client Session - 与服务器的实际连接(属于 MCP Python SDK 的一部分)

客户端会话在使用完毕后需要进行适当的资源清理。这就是为什么我们将其封装在自定义的 MCP Client 类中——以便自动处理所有清理工作。
客户端如何融入我们的应用程序
还记得我们的应用程序流程吗?我们的 CLI 代码需要对 MCP 服务器执行两项主要操作:

- 获取可用工具列表以发送给 Claude
- 在 Claude 请求时执行工具
MCP 客户端通过简单的方法调用提供这些功能,供我们的应用程序代码使用。
实现核心方法
我们需要在客户端中实现两个关键方法:list_tools() 和 call_tool()。
List Tools 方法
此方法从服务器获取所有可用工具:
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.tools这很简单——我们访问我们的会话(与服务器的连接),调用内置的 list_tools() 函数,并从结果中返回工具。
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)我们将工具名称和输入参数(由 Claude 提供)传递给服务器,并返回结果。
测试客户端
为了测试我们的实现,我们可以直接运行客户端。该文件包含一个测试工具,用于连接到我们的 MCP 服务器并调用我们的方法:
async with MCPClient(
command="uv", args=["run", "mcp_server.py"]
) as client:
result = await client.list_tools()
print(result)运行此测试时,我们应该能看到打印出的工具定义,包括我们之前创建的 read_doc_contents 和 edit_document 工具。
整合所有内容
现在我们的客户端可以列出工具并调用它们,我们可以测试完整的流程了。当我们运行主应用程序并向 Claude 询问有关文档的问题时:
- 我们的代码使用客户端获取可用工具
- 这些工具与用户的问题一起发送给 Claude
- Claude 决定使用
read_doc_contents工具 - 我们的代码使用客户端执行该工具
- 结果被发送回 Claude,然后 Claude 向用户做出响应
例如,询问"report.pdf 文档的内容是什么?"将触发 Claude 使用我们的文档读取工具,我们将获得关于我们在服务器中设置的 20m 冷凝塔文档的信息。
客户端充当我们的应用程序逻辑与 MCP 服务器之间的桥梁,使我们能够轻松访问服务器功能,而无需担心底层连接细节。