既然我们的 MCP 服务器已经可以正常工作,现在是时候构建客户端了。客户端是让我们的应用程序能够与 MCP 服务器通信并访问其功能的部分。
理解客户端架构
在深入代码之前,让我们先澄清一个关于 MCP 项目的重要观点。通常情况下,您会实现 MCP 客户端或 MCP 服务器中的一个——而不是两者都实现。我们在这个项目中同时构建两者,只是为了让您看到它们如何协同工作。

MCP 客户端由两个协同工作的主要组件组成:

- MCP Client - 我们创建的一个自定义类,用于让使用会话更加简便
- Client Session - 与服务器的实际连接(属于 MCP Python SDK 的一部分)
客户端会话处理底层通信,但在程序关闭时需要仔细进行资源清理。这就是为什么我们将其封装在自己的类中——以便自动管理该清理工作。
客户端如何融入我们的应用程序
还记得我们的应用程序流程图吗?客户端在两个关键时刻发挥着至关重要的作用:

我们的 CLI 代码使用客户端来:
- 获取要发送给 Claude 的可用工具列表
- 在 Claude 请求时执行工具
实现核心客户端函数
让我们实现两个基本函数:list_tools 和 call_tool。
对于 list_tools,我们需要连接到我们的会话并请求可用的工具:
async def list_tools(self) -> list[types.Tool]:
result = await self.session().list_tools()
return result.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)就是这样!会话为我们处理了所有复杂的通信细节。
测试客户端
客户端文件底部包含一个简单的测试工具。您可以直接运行它来验证一切是否正常工作:
uv run mcp_client.py这将连接到您的 MCP 服务器并打印出可用的工具。您应该会看到显示您的工具定义的输出,包括名称、描述和输入模式。
重要的模式差异
这里有一个您需要了解的陷阱:MCP 工具定义与 Claude 所期望的内容并不完全匹配。MCP 规范有自己的工具模式格式,这与 Bedrock 所要求的格式略有不同。
不用担心——项目中已经有代码可以自动处理这种转换。core/bedrock.py 中的 to_bedrock_tools 函数会将 MCP 工具定义转换为 Claude 能够理解的格式。
使用 Claude 进行测试
既然服务器和客户端都已正常工作,您可以测试完整的流程了。尝试运行您的主应用程序,并让 Claude 读取一份文档:
uv run main.py然后询问:"report.pdf 文档的内容是什么?"
Claude 将会:
- 从您的客户端接收可用工具列表
- 决定使用 read_doc_contents 工具
- 您的客户端将在 MCP 服务器上执行该工具
- Claude 将接收文档内容并作出响应
客户端充当您的应用程序代码与 MCP 服务器之间的桥梁,使向 Claude 和系统其他部分公开服务器功能变得轻而易举。