面向客户端开发者

开始构建您自己的客户端,以便与所有 MCP 服务器集成。

在本教程中,您将学习如何构建一个由 LLM 驱动的聊天机器人客户端,并将其连接到 MCP 服务器。在此之前,建议您先阅读 服务器快速入门,该指南将帮助您完成搭建第一个服务器的基础知识。

支持的语言:

  • Python
  • Node
  • Java

本教程的完整代码可在此找到。


系统要求

在开始之前,请确保您的系统满足以下要求:

  • Mac 或 Windows 计算机
  • 已安装最新的 Python 版本
  • 已安装最新版本的 uv

设置开发环境

首先,使用 uv 创建一个新的 Python 项目:

  1. # 创建项目目录
  2. uv init mcp-client
  3. cd mcp-client
  4. # 创建虚拟环境
  5. uv venv
  6. # 激活虚拟环境
  7. # 在 Windows 上:
  8. .venv\Scripts\activate
  9. # 在 Unix 或 macOS 上:
  10. source .venv/bin/activate
  11. # 安装所需的依赖包
  12. uv add mcp anthropic python-dotenv
  13. # 移除默认生成的示例文件
  14. rm hello.py
  15. # 创建我们的主文件
  16. touch client.py

配置 API 密钥

您需要从 Anthropic 控制台 获取一个 API 密钥。

创建 .env 文件以存储密钥:

  1. # 创建 .env 文件
  2. touch .env

.env 文件中添加您的密钥:

  1. ANTHROPIC_API_KEY=<your key here>

然后将 .env 文件添加到 .gitignore,避免泄露密钥:

  1. echo ".env" >> .gitignore

请确保您的 ANTHROPIC_API_KEY 处于安全状态!


创建客户端

基本的客户端结构

首先,设置必要的导入,并创建基本的客户端类:

  1. import asyncio
  2. from typing import Optional
  3. from contextlib import AsyncExitStack
  4. from mcp import ClientSession, StdioServerParameters
  5. from mcp.client.stdio import stdio_client
  6. from anthropic import Anthropic
  7. from dotenv import load_dotenv
  8. load_dotenv() # 从 .env 文件加载环境变量
  9. class MCPClient:
  10. def __init__(self):
  11. # 初始化会话和客户端对象
  12. self.session: Optional[ClientSession] = None
  13. self.exit_stack = AsyncExitStack()
  14. self.anthropic = Anthropic()
  15. # 方法将在这里定义

管理服务器连接

接下来,我们实现连接 MCP 服务器的方法:

  1. async def connect_to_server(self, server_script_path: str):
  2. """连接到 MCP 服务器
  3. 参数:
  4. server_script_path: 服务器脚本文件的路径 (.py 或 .js)
  5. """
  6. is_python = server_script_path.endswith('.py')
  7. is_js = server_script_path.endswith('.js')
  8. if not (is_python or is_js):
  9. raise ValueError("服务器脚本必须是 .py 或 .js 文件")
  10. command = "python" if is_python else "node"
  11. server_params = StdioServerParameters(
  12. command=command,
  13. args=[server_script_path],
  14. env=None
  15. )
  16. stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
  17. self.stdio, self.write = stdio_transport
  18. self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
  19. await self.session.initialize()
  20. # 列出可用工具
  21. response = await self.session.list_tools()
  22. tools = response.tools
  23. print("\n成功连接服务器,以下是可用工具:", [tool.name for tool in tools])

处理查询请求

现在,添加核心功能,用于处理用户查询并调用服务器提供的工具:

  1. async def process_query(self, query: str) -> str:
  2. """使用 Claude 和可用工具处理查询"""
  3. messages = [
  4. {
  5. "role": "user",
  6. "content": query
  7. }
  8. ]
  9. response = await self.session.list_tools()
  10. available_tools = [{
  11. "name": tool.name,
  12. "description": tool.description,
  13. "input_schema": tool.inputSchema
  14. } for tool in response.tools]
  15. # 调用 Claude API 处理初始查询
  16. response = self.anthropic.messages.create(
  17. model="claude-3-5-sonnet-20241022",
  18. max_tokens=1000,
  19. messages=messages,
  20. tools=available_tools
  21. )
  22. # 处理响应,并执行工具调用
  23. final_text = []
  24. assistant_message_content = []
  25. for content in response.content:
  26. if content.type == 'text':
  27. final_text.append(content.text)
  28. assistant_message_content.append(content)
  29. elif content.type == 'tool_use':
  30. tool_name = content.name
  31. tool_args = content.input
  32. # 执行工具调用
  33. result = await self.session.call_tool(tool_name, tool_args)
  34. final_text.append(f"[调用工具 {tool_name} 参数: {tool_args}]")
  35. assistant_message_content.append(content)
  36. messages.append({
  37. "role": "assistant",
  38. "content": assistant_message_content
  39. })
  40. messages.append({
  41. "role": "user",
  42. "content": [
  43. {
  44. "type": "tool_result",
  45. "tool_use_id": content.id,
  46. "content": result.content
  47. }
  48. ]
  49. })
  50. # 获取 Claude 的后续响应
  51. response = self.anthropic.messages.create(
  52. model="claude-3-5-sonnet-20241022",
  53. max_tokens=1000,
  54. messages=messages,
  55. tools=available_tools
  56. )
  57. final_text.append(response.content[0].text)
  58. return "\n".join(final_text)

交互式聊天界面

  1. async def chat_loop(self):
  2. """运行交互式聊天"""
  3. print("\nMCP 客户端已启动!")
  4. print("请输入查询,或输入 'quit' 退出。")
  5. while True:
  6. try:
  7. query = input("\n输入查询: ").strip()
  8. if query.lower() == 'quit':
  9. break
  10. response = await self.process_query(query)
  11. print("\n" + response)
  12. except Exception as e:
  13. print(f"\n错误: {str(e)}")

运行客户端

要运行您的客户端,并连接到 MCP 服务器:

  1. uv run client.py path/to/server.py # 连接 Python 服务器
  2. uv run client.py path/to/build/index.js # 连接 Node 服务器

如果您正在继续服务器快速入门教程(例如天气查询服务器),命令可能如下:

  1. python client.py .../weather/src/weather/server.py

客户端将会:

  1. 连接到指定服务器
  2. 列出可用工具
  3. 启动交互式聊天,支持:
    • 发送查询
    • 执行工具调用
    • 获取 Claude 生成的回复

常见问题排查

服务器路径问题

  • 确保服务器脚本路径正确
  • 如果相对路径无效,请使用绝对路径
  • Windows 用户请使用 /\\ 作为路径分隔符

示例:

  1. # 相对路径
  2. uv run client.py ./server/weather.py
  3. # 绝对路径
  4. uv run client.py /Users/username/projects/mcp-server/weather.py
  5. # Windows 路径
  6. uv run client.py C:/projects/mcp-server/weather.py
  7. uv run client.py C:\\projects\\mcp-server\\weather.py

响应延迟

  • 首次查询可能需要 30 秒返回(服务器初始化、Claude 处理查询)
  • 后续查询通常更快
  • 避免在初始加载期间中断进程

这样,您就可以使用 MCP 搭建自己的 LLM 驱动的聊天机器人客户端了! 🚀