03-MCP客户端开发实战
本文详细介绍了MCP客户端开发实战,涵盖Stdio和HTTP两种传输模式的实现。主要内容包括: MCP客户端架构设计,包含传输层、会话层、API层和错误处理等核心组件 Stdio模式客户端的完整实现,包括服务器参数配置、会话初始化和工具调用示例 演示了数学计算工具的调用流程,如加法、减法、乘法等基础运算 展示了资源列表获取和读取功能 文章提供了完整的代码示例,帮助开发者快速构建MCP客户端,实现与
·
03-MCP客户端开发实战
概述
在前两篇文章中,我们分别介绍了 MCP 协议的基本概念和如何构建 MCP 服务器。本文将详细介绍如何开发 MCP 客户端,包括 stdio 和 HTTP 两种传输方式的实现,以及如何调用工具、读取资源和获取提示模板。
MCP客户端架构
客户端职责
MCP 客户端负责与 MCP 服务器建立连接,并管理与服务器的交互:
- 建立和维护服务器连接
- 发现服务器提供的工具、资源和提示模板
- 调用工具并处理返回结果
- 读取资源并解析数据
- 获取提示模板并应用
核心组件
- 传输层: stdio 或 HTTP 连接管理
- 会话层: ClientSession 管理
- API 层: 工具调用、资源读取、提示模板获取
- 错误处理: 异常捕获和重试机制
Stdio模式客户端
基础结构
Stdio 模式通过标准输入输出与服务器进程通信。
import asyncio
from mcp.client.stdio import stdio_client
from mcp import ClientSession, StdioServerParameters
async def main():
# 配置服务器参数
server_params = StdioServerParameters(
command="python",
args=["math_mcp_server_stdio.py"]
)
# 建立连接
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
if __name__ == "__main__":
asyncio.run(main())
服务器参数配置
StdioServerParameters 用于配置 stdio 连接:
server_params = StdioServerParameters(
command="python", # 执行命令
args=["math_mcp_server_stdio.py"], # 命令参数
env=None, # 环境变量(可选)
cwd=None # 工作目录(可选)
)
会话初始化
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化会话
capabilities = await session.initialize()
# 检查服务器能力
print(f"Server capabilities: {capabilities}")
完整的 Stdio 客户端
以下是完整的 Stdio 客户端实现:
import asyncio
from mcp.client.stdio import stdio_client
from mcp import ClientSession, StdioServerParameters
async def main():
# 配置服务器参数
server_params = StdioServerParameters(
command="python",
args=["math_mcp_server_stdio.py"]
)
print("=" * 70)
print("MCP数学计算服务器客户端演示")
print("MCP Math Server Client Demo")
print("=" * 70)
print()
# 连接到服务器
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化会话
await session.initialize()
print("成功连接到服务器!")
print("Successfully connected to server!")
print()
# 第1部分: 列出所有可用工具
print("=" * 70)
print("第1部分: 可用工具列表")
print("Part 1: Available Tools")
print("=" * 70)
tools = await session.list_tools()
print(f"\n找到 {len(tools.tools)} 个工具 (Found {len(tools.tools)} tools):")
print()
for idx, tool in enumerate(tools.tools, 1):
print(f"{idx}. {tool.name}")
print(f" 描述 (Description): {tool.description}")
print()
# 第2部分: 调用基础运算工具
print("=" * 70)
print("第2部分: 基础运算示例")
print("Part 2: Basic Arithmetic Examples")
print("=" * 70)
print()
# 加法
print("调用加法工具: add(10, 20)")
result = await session.call_tool("add", {"a": 10, "b": 20})
print(f"结果 (Result): {result.content[0].text}")
print()
# 减法
print("调用减法工具: subtract(50, 30)")
result = await session.call_tool("subtract", {"a": 50, "b": 30})
print(f"结果 (Result): {result.content[0].text}")
print()
# 乘法
print("调用乘法工具: multiply(7, 8)")
result = await session.call_tool("multiply", {"a": 7, "b": 8})
print(f"结果 (Result): {result.content[0].text}")
print()
# 除法
print("调用除法工具: divide(100, 4)")
result = await session.call_tool("divide", {"a": 100, "b": 4})
print(f"结果 (Result): {result.content[0].text}")
print()
# 幂运算
print("调用幂运算工具: power(2, 10)")
result = await session.call_tool("power", {"base": 2, "exponent": 10})
print(f"结果 (Result): {result.content[0].text}")
print()
# 平方根
print("调用平方根工具: sqrt(144)")
result = await session.call_tool("sqrt", {"number": 144})
print(f"结果 (Result): {result.content[0].text}")
print()
# 阶乘
print("调用阶乘工具: factorial(5)")
result = await session.call_tool("factorial", {"n": 5})
print(f"结果 (Result): {result.content[0].text}")
print()
# 第3部分: 列出所有可用资源
print("=" * 70)
print("第3部分: 可用资源列表")
print("Part 3: Available Resources")
print("=" * 70)
resources = await session.list_resources()
print(f"\n找到 {len(resources.resources)} 个资源 (Found {len(resources.resources)} resources):")
print()
for idx, resource in enumerate(resources.resources, 1):
print(f"{idx}. {resource.uri}")
print(f" 名称 (Name): {resource.name}")
print()
# 第4部分: 读取各种资源
print("=" * 70)
print("第4部分: 读取资源示例")
print("Part 4: Read Resources Examples")
print("=" * 70)
print()
# 读取圆周率
print("读取圆周率资源: constant://pi")
resource = await session.read_resource("constant://pi")
print(f"圆周率值 (Pi value): {resource.contents[0].text}")
print()
# 读取自然常数
print("读取自然常数资源: constant://e")
resource = await session.read_resource("constant://e")
print(f"自然常数 (Euler's number): {resource.contents[0].text}")
print()
# 读取黄金比例
print("读取黄金比例资源: constant://golden_ratio")
resource = await session.read_resource("constant://golden_ratio")
print(f"黄金比例 (Golden ratio): {resource.contents[0].text}")
print()
# 读取个性化问候
print("读取个性化问候: greeting://Alice")
resource = await session.read_resource("greeting://Alice")
print(f"问候 (Greeting): {resource.contents[0].text}")
print()
# 读取配置
print("读取应用配置: config://app")
resource = await session.read_resource("config://app")
print("配置信息 (Configuration):")
for line in resource.contents[0].text.split('\n'):
print(f" {line}")
print()
# 第5部分: 列出所有提示模板
print("=" * 70)
print("第5部分: 可用提示模板列表")
print("Part 5: Available Prompts")
print("=" * 70)
prompts = await session.list_prompts()
print(f"\n找到 {len(prompts.prompts)} 个提示模板 (Found {len(prompts.prompts)} prompts):")
print()
for idx, prompt in enumerate(prompts.prompts, 1):
print(f"{idx}. {prompt.name}")
print(f" 描述 (Description): {prompt.description}")
print()
# 第6部分: 获取提示模板
print("=" * 70)
print("第6部分: 获取提示模板示例")
print("Part 6: Get Prompt Examples")
print("=" * 70)
print()
# 获取数学问题提示
print("获取数学问题提示: example_prompt(什么是1+1?)")
prompt = await session.get_prompt("example_prompt", {"question": "什么是1+1?"})
print("提示内容 (Prompt content):")
for line in prompt.messages[0].content.text.split('\n'):
print(f" {line}")
print()
# 获取计算指导
print("获取乘法指导: calculation_guide(multiplication)")
prompt = await session.get_prompt("calculation_guide", {"operation": "multiplication"})
print("指导内容 (Guide content):")
for line in prompt.messages[0].content.text.split('\n'):
print(f" {line}")
print()
print("=" * 70)
print("演示完成!")
print("Demo completed!")
print("=" * 70)
print()
print("总结 (Summary):")
print(" - 成功连接到MCP服务器")
print(" - 列出了所有可用的工具、资源和提示模板")
print(" - 调用了7个不同的数学运算工具")
print(" - 读取了6个不同的资源")
print(" - 获取了2个提示模板")
print()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n\n程序被用户中断 (Program interrupted by user)")
except Exception as e:
print(f"\n\n发生错误 (Error occurred): {e}")
import traceback
traceback.print_exc()
运行 Stdio 客户端
# 确保服务器正在运行
python math_mcp_server_stdio.py
# 在另一个终端运行客户端
python math_mcp_client_stdio.py
HTTP模式客户端
基础结构
HTTP 模式通过 HTTP 协议与服务器通信。
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
# 服务器 URL
server_url = "http://localhost:8000/mcp"
# 建立连接
async with streamablehttp_client(server_url) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
if __name__ == "__main__":
asyncio.run(main())
完整的 HTTP 客户端
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
# 数学HTTP服务器URL
math_server_url = "http://localhost:8000/mcp"
async def main():
print("=" * 70)
print("MCP数学HTTP服务器客户端演示")
print("MCP Math HTTP Server Client Demo")
print("=" * 70)
print()
async with streamablehttp_client(math_server_url) as (read, write, _):
async with ClientSession(read, write) as session:
# 初始化会话
await session.initialize()
print("成功连接到数学HTTP服务器!")
print("Successfully connected to Math HTTP server!")
print()
# 列出所有可用工具
print("=" * 70)
print("第1部分: 可用工具列表")
print("Part 1: Available Tools")
print("=" * 70)
response_tools = await session.list_tools()
print(f"\n找到 {len(response_tools.tools)} 个工具 (Found {len(response_tools.tools)} tools):")
print()
for idx, tool in enumerate(response_tools.tools, 1):
print(f"{idx}. 工具名称 (Name): {tool.name}")
print(f" 描述 (Description): {tool.description}")
print()
# 列出所有资源
print("=" * 70)
print("第2部分: 可用资源列表")
print("Part 2: Available Resources")
print("=" * 70)
response_resources = await session.list_resources()
print(f"\n找到 {len(response_resources.resources)} 个资源 (Found {len(response_resources.resources)} resources):")
print()
for idx, resource in enumerate(response_resources.resources, 1):
print(f"{idx}. 资源URI (URI): {resource.uri}")
print(f" 资源名称 (Name): {resource.name}")
print()
# 列出所有提示模板
print("=" * 70)
print("第3部分: 可用提示模板列表")
print("Part 3: Available Prompts")
print("=" * 70)
response_prompts = await session.list_prompts()
print(f"\n找到 {len(response_prompts.prompts)} 个提示模板 (Found {len(response_prompts.prompts)} prompts):")
print()
for idx, prompt in enumerate(response_prompts.prompts, 1):
print(f"{idx}. 提示名称 (Name): {prompt.name}")
print(f" 描述 (Description): {prompt.description}")
print()
# 调用工具示例
print("=" * 70)
print("第4部分: 调用工具示例")
print("Part 4: Tool Call Examples")
print("=" * 70)
print()
# 示例1: 加法
print("调用加法工具: add(10, 20)")
result = await session.call_tool("add", arguments={"a": 10, "b": 20})
print(f"结果 (Result): {result.content[0].text}")
print()
# 示例2: 乘法
print("调用乘法工具: multiply(7, 8)")
result = await session.call_tool("multiply", arguments={"a": 7, "b": 8})
print(f"结果 (Result): {result.content[0].text}")
print()
# 示例3: 幂运算
print("调用幂运算工具: power(2, 10)")
result = await session.call_tool("power", arguments={"base": 2, "exponent": 10})
print(f"结果 (Result): {result.content[0].text}")
print()
# 示例4: 平方根
print("调用平方根工具: sqrt(144)")
result = await session.call_tool("sqrt", arguments={"number": 144})
print(f"结果 (Result): {result.content[0].text}")
print()
# 读取资源示例
print("=" * 70)
print("第5部分: 读取资源示例")
print("Part 5: Read Resource Examples")
print("=" * 70)
print()
# 示例5: 读取圆周率
print("读取圆周率资源: constant://pi")
resource = await session.read_resource("constant://pi")
print(f"圆周率值 (Pi value): {resource.contents[0].text}")
print()
# 示例6: 读取自然常数
print("读取自然常数资源: constant://e")
resource = await session.read_resource("constant://e")
print(f"自然常数 (Euler's number): {resource.contents[0].text}")
print()
# 获取提示模板示例
print("=" * 70)
print("第6部分: 获取提示模板示例")
print("Part 6: Get Prompt Examples")
print("=" * 70)
print()
# 示例7: 获取数学问题提示
print("获取数学问题提示: example_prompt(什么是10+2?)")
prompt = await session.get_prompt("example_prompt", arguments={"question": "What is 10+2?"})
print(f"提示内容 (Prompt content):")
print(f" {prompt.messages[0].content.text}")
print()
print("=" * 70)
print("演示完成!")
print("Demo completed!")
print("=" * 70)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n\n程序被用户中断 (Program interrupted by user)")
except Exception as e:
print(f"\n\n发生错误 (Error occurred): {e}")
import traceback
traceback.print_exc()
运行 HTTP 客户端
# 终端1: 启动 HTTP 服务器
python math_mcp_server_http.py
# 终端2: 运行客户端
python math_mcp_client_http.py
核心API详解
1. 工具调用
列出工具
# 获取所有可用工具
tools = await session.list_tools()
# 遍历工具列表
for tool in tools.tools:
print(f"工具名称: {tool.name}")
print(f"工具描述: {tool.description}")
print(f"输入Schema: {tool.inputSchema}")
调用工具
# 调用加法工具
result = await session.call_tool("add", {"a": 10, "b": 20})
# 获取结果
for content in result.content:
if hasattr(content, 'text'):
print(f"结果: {content.text}")
错误处理
try:
result = await session.call_tool("divide", {"a": 10, "b": 0})
except Exception as e:
print(f"工具调用失败: {e}")
2. 资源读取
列出资源
# 获取所有可用资源
resources = await session.list_resources()
# 遍历资源列表
for resource in resources.resources:
print(f"资源URI: {resource.uri}")
print(f"资源名称: {resource.name}")
print(f"资源描述: {resource.description}")
读取资源
# 读取静态资源
resource = await session.read_resource("constant://pi")
print(f"圆周率: {resource.contents[0].text}")
# 读取动态资源
resource = await session.read_resource("greeting://Alice")
print(f"问候: {resource.contents[0].text}")
# 读取JSON资源
resource = await session.read_resource("config://app")
import json
config = json.loads(resource.contents[0].text)
print(f"配置: {config}")
3. 提示模板获取
列出提示模板
# 获取所有可用提示模板
prompts = await session.list_prompts()
# 遍历提示模板列表
for prompt in prompts.prompts:
print(f"提示名称: {prompt.name}")
print(f"提示描述: {prompt.description}")
print(f"参数: {prompt.arguments}")
获取提示模板
# 获取系统提示
prompt = await session.get_prompt("system_prompt")
print(f"系统提示: {prompt.messages[0].content.text}")
# 获取带参数的提示
prompt = await session.get_prompt("example_prompt", {"question": "What is 1+1?"})
print(f"提示: {prompt.messages[0].content.text}")
高级功能
1. 并发调用
async def concurrent_calls():
tasks = [
session.call_tool("add", {"a": 1, "b": 2}),
session.call_tool("multiply", {"a": 3, "b": 4}),
session.call_tool("divide", {"a": 10, "b": 2})
]
results = await asyncio.gather(*tasks)
for i, result in enumerate(results):
print(f"结果{i+1}: {result.content[0].text}")
asyncio.run(concurrent_calls())
2. 重试机制
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10))
async def safe_call_tool(session, tool_name, arguments):
return await session.call_tool(tool_name, arguments)
3. 日志记录
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def logged_call(session, tool_name, arguments):
logger.info(f"调用工具: {tool_name} 参数: {arguments}")
try:
result = await session.call_tool(tool_name, arguments)
logger.info(f"工具调用成功: {result.content[0].text}")
return result
except Exception as e:
logger.error(f"工具调用失败: {e}")
raise
4. 连接池管理
class MCPClientPool:
def __init__(self, server_url, max_connections=5):
self.server_url = server_url
self.max_connections = max_connections
self.clients = []
async def get_client(self):
if len(self.clients) < self.max_connections:
read, write, _ = await streamablehttp_client(self.server_url).__aenter__()
client = ClientSession(read, write)
await client.__aenter__()
await client.initialize()
self.clients.append(client)
return self.clients[-1]
async def close_all(self):
for client in self.clients:
await client.__aexit__(None, None, None)
self.clients.clear()
客户端最佳实践
1. 资源管理
# 使用 async with 确保资源释放
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 客户端逻辑
pass
# 自动释放资源
2. 错误处理
async def robust_client():
try:
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 客户端逻辑
except ConnectionError as e:
print(f"连接失败: {e}")
except TimeoutError:
print("请求超时")
except Exception as e:
print(f"未知错误: {e}")
3. 配置管理
import os
from dataclasses import dataclass
@dataclass
class ClientConfig:
server_url: str
timeout: int = 30
max_retries: int = 3
log_level: str = "INFO"
def load_config():
return ClientConfig(
server_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000/mcp"),
timeout=int(os.getenv("MCP_TIMEOUT", "30")),
max_retries=int(os.getenv("MCP_MAX_RETRIES", "3"))
)
4. 性能优化
import functools
def cache_tool_call(ttl=60):
"""工具调用结果缓存装饰器"""
cache = {}
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key in cache:
return cache[key]
result = await func(*args, **kwargs)
cache[key] = result
return result
return wrapper
return decorator
测试客户端
单元测试
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_call_tool():
async with patch('mcp.client.stdio.stdio_client') as mock_client:
mock_session = AsyncMock()
mock_session.initialize = AsyncMock()
mock_session.call_tool = AsyncMock(return_value=Mock(content=[Mock(text="30")]))
mock_client.return_value.__aenter__.return_value = (None, None)
mock_client.return_value.__aexit__.return_value = None
async with stdio_client(None) as (read, write):
async with ClientSession(read, write) as session:
result = await session.call_tool("add", {"a": 10, "b": 20})
assert result.content[0].text == "30"
集成测试
@pytest.mark.asyncio
async def test_math_server_integration():
server_params = StdioServerParameters(
command="python",
args=["math_mcp_server_stdio.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 测试工具调用
result = await session.call_tool("add", {"a": 10, "b": 20})
assert "30" in result.content[0].text
# 测试资源读取
resource = await session.read_resource("constant://pi")
assert "3.14" in resource.contents[0].text
故障排查
问题 1: 连接失败
错误信息:
ConnectionError: Unable to connect to server
解决方案:
- 检查服务器是否运行
- 验证服务器URL是否正确
- 检查防火墙设置
问题 2: 工具调用超时
错误信息:
TimeoutError: Tool call timed out
解决方案:
- 增加超时时间
- 优化工具执行效率
- 使用并发调用
问题 3: 参数验证失败
错误信息:
ValidationError: Invalid arguments for tool 'add'
解决方案:
- 检查参数类型
- 验证参数值范围
- 查看工具Schema定义
阅读顺序建议
- 01-MCP协议入门指南: 了解 MCP 基本概念和核心组件
- 02-快速构建MCP服务器: 使用 FastMCP 构建服务器
- 03-MCP客户端开发实战: 开发 stdio 和 HTTP 客户端
- 04-LLM与MCP集成实践: 集成到 LangGraph 构建智能代理
- 05-多服务器架构与最佳实践: 多服务器架构和生产部署
总结
本文详细介绍了 MCP 客户端的开发,包括:
- Stdio 模式客户端的完整实现
- HTTP 模式客户端的完整实现
- 核心 API 的详细使用方法
- 高级功能和最佳实践
- 测试和故障排查
在下一篇《LLM与MCP集成实践》中,我们将学习如何将 MCP 工具集成到 LangGraph 中,构建智能代理应用。
参考资源
文章标签
MCP客户端, Python开发, stdio传输, HTTP客户端, 工具调用, 资源读取, 提示模板, 异步编程, 客户端开发
更多推荐


所有评论(0)