第6章:MCP Client集成
在前面的章节中,我们学习了如何开发 MCP Server,定义 Tools 和 Resources。本章将学习如何开发 MCP Client——在自己的应用程序中连接和使用 MCP Server 的客户端程序。
一、什么是 MCP Client
MCP Client 是 MCP 架构中的另一重要角色,它的职责是:
- 连接 Server:与 MCP Server 建立通信连接
- 发现能力:获取 Server 提供的 Tools、Resources、Prompts
- 调用工具:根据需要调用 Server 的 Tools
- 处理结果:接收并处理 Server 返回的结果
类比理解:
• MCP Server = 餐厅(提供菜品/服务)
• MCP Client = 顾客(点菜、用餐)
• MCP 协议 = 菜单和交流语言
• MCP Server = 餐厅(提供菜品/服务)
• MCP Client = 顾客(点菜、用餐)
• MCP 协议 = 菜单和交流语言
Client 与 Server 的关系
┌─────────────────┐ MCP Protocol ┌─────────────────┐
│ MCP Client │ ◄────────────────────────► │ MCP Server │
│ (你的应用程序) │ │ (提供Tools服务) │
│ │ │ │
│ 1. 连接 Server │ │ 1. 启动并监听 │
│ 2. 获取工具列表 │ │ 2. 注册工具 │
│ 3. 调用工具 │ │ 3. 执行工具逻辑 │
│ 4. 处理结果 │ │ 4. 返回结果 │
└─────────────────┘ └─────────────────┘
二、Client 使用场景
开发 MCP Client 可以让你的应用程序获得强大的 AI 扩展能力:
| 场景 | 说明 | 示例 |
|---|---|---|
| 智能助手 | 开发命令行或桌面 AI 助手 | 可以执行文件操作、查询数据库的 AI 助手 |
| 自动化工具 | 结合 LLM 和工具实现自动化 | 自动代码审查、文档生成工具 |
| IDE 插件 | 在编辑器中集成 AI 能力 | VS Code 插件,支持代码补全、重构 |
| 数据分析 | 让 AI 直接查询和分析数据 | 用自然语言查询数据库、生成报表 |
| 运维工具 | 服务器管理和监控 | 自然语言查询日志、重启服务 |
三、TypeScript Client 开发
MCP SDK 提供了完整的 Client 支持。以下是 TypeScript Client 的核心开发步骤:
1. 安装依赖
npm install @modelcontextprotocol/sdk
2. 基本 Client 代码结构
// client.js - MCP Client 基础示例
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
/**
* 创建 MCP Client 并连接到 Server
*/
async function createClient() {
// 步骤 1: 创建 Client 实例
// name 和 version 用于标识你的 Client 应用
const client = new Client({
name: 'my-mcp-client',
version: '1.0.0',
});
// 步骤 2: 创建传输层(stdio 方式)
// 这里连接到本地的 server.js
const transport = new StdioClientTransport({
command: 'node',
args: ['server.js'], // Server 入口文件路径
env: process.env, // 传递环境变量
});
// 步骤 3: 建立连接
await client.connect(transport);
console.log('✅ 已连接到 MCP Server');
return client;
}
传输层说明:
•
•
•
StdioClientTransport:通过标准输入输出通信,适合本地进程•
SseClientTransport:通过 HTTP SSE 通信,适合远程 Server
3. 完整 TypeScript Client 示例
// full-client.js - 完整的 MCP Client 示例
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import readline from 'readline';
class MCPClient {
constructor() {
this.client = null;
this.transport = null;
}
/**
* 连接到 MCP Server
* @param {string} serverPath - Server 脚本路径
*/
async connect(serverPath) {
// 创建 Client 实例
this.client = new Client({
name: 'mcp-demo-client',
version: '1.0.0',
});
// 创建 stdio 传输层
this.transport = new StdioClientTransport({
command: 'node',
args: [serverPath],
});
// 建立连接
await this.client.connect(this.transport);
console.log('✅ 已成功连接到 MCP Server');
}
/**
* 获取 Server 提供的所有 Tools
* @returns {Array} 工具列表
*/
async listTools() {
const response = await this.client.listTools();
return response.tools || [];
}
/**
* 调用指定的 Tool
* @param {string} name - 工具名称
* @param {object} args - 工具参数
* @returns {object} 调用结果
*/
async callTool(name, args) {
const result = await this.client.callTool({
name: name,
arguments: args,
});
return result;
}
/**
* 关闭连接
*/
async close() {
if (this.transport) {
await this.transport.close();
console.log('🔌 已断开连接');
}
}
}
// ===== 使用示例 =====
async function main() {
const mcpClient = new MCPClient();
try {
// 1. 连接到 Server(假设 Server 在同级目录的 server.js)
await mcpClient.connect('./server.js');
// 2. 获取可用的 Tools 列表
console.log('\n📋 可用工具列表:');
const tools = await mcpClient.listTools();
tools.forEach((tool, index) => {
console.log(` ${index + 1}. ${tool.name}: ${tool.description}`);
});
// 3. 调用 calculator 工具
console.log('\n🔧 测试调用 calculator 工具:');
const calcResult = await mcpClient.callTool('calculator', {
expression: '100 * 2 + 50',
});
console.log('结果:', calcResult.content[0].text);
// 4. 调用时间查询工具
console.log('\n🔧 测试调用 get_current_time 工具:');
const timeResult = await mcpClient.callTool('get_current_time', {
format: 'full',
});
console.log('结果:', timeResult.content[0].text);
} catch (error) {
console.error('❌ 错误:', error.message);
} finally {
// 5. 关闭连接
await mcpClient.close();
}
}
// 运行
main();
四、调用 Tools 流程
在实际应用中,与 LLM 集成时的典型调用流程如下:
┌─────────────┐ 1. 用户提问 ┌─────────────┐
│ 用户 │ ───────────────────► │ Client 应用 │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ 2. 获取工具列表 │
│ listTools() │
└──────┬──────┘
│
▼
┌─────────────┐ 3. 发送问题和工具列表 ┌─────────────┐
│ Client 应用 │ ───────────────────────► │ LLM │
│ │ │ (AI模型) │
│ 4. 执行工具 │ ◄─────────────────────── │ │
│ callTool() │ 返回要调用的工具名称 │ 决定调用 │
└──────┬──────┘ 和参数 └─────────────┘
│
▼
┌─────────────┐
│ MCP Server │
│ (执行逻辑) │
└──────┬──────┘
│
▼
┌─────────────┐ 5. 返回工具执行结果 ┌─────────────┐
│ Client 应用 │ ───────────────────────► │ LLM │
│ │ │ │
│ 6. 展示最终 │ ◄─────────────────────── │ 生成回复 │
│ 回答 │ 返回给用户看的回答 │ │
└──────┬──────┘ └─────────────┘
│
▼
┌─────────────┐
│ 用户 │
└─────────────┘
核心 API 详解
| API | 功能 | 返回值 |
|---|---|---|
client.listTools() |
获取 Server 的所有可用工具 | { tools: [...] } |
client.callTool() |
调用指定工具 | { content: [...], isError?: boolean } |
client.listResources() |
获取 Server 的所有可用资源 | { resources: [...] } |
client.readResource() |
读取指定资源 | { contents: [...] } |
client.listPrompts() |
获取 Server 的所有提示模板 | { prompts: [...] } |
五、Python Client 开发
MCP 也提供了 Python SDK,以下是等效的 Python Client 实现:
1. 安装依赖
pip install mcp
2. Python Client 完整代码
#!/usr/bin/env python3
"""
MCP Python Client 示例
演示如何连接 MCP Server 并调用 Tools
"""
import asyncio
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class MCPPythonClient:
"""MCP Client 封装类"""
def __init__(self):
self.session = None
self.client = None
async def connect(self, server_script: str):
"""
连接到 MCP Server
Args:
server_script: Server Python 脚本路径
"""
# 配置 Server 参数
server_params = StdioServerParameters(
command="python",
args=[server_script],
env=None
)
# 建立 stdio 连接
self.client = stdio_client(server_params)
self.read, self.write = await self.client.__aenter__()
# 创建会话
self.session = await ClientSession(self.read, self.write).__aenter__()
# 初始化连接
await self.session.initialize()
print("✅ 已成功连接到 MCP Server")
async def list_tools(self):
"""获取可用的 Tools 列表"""
response = await self.session.list_tools()
return response.tools
async def call_tool(self, name: str, arguments: dict):
"""
调用指定的 Tool
Args:
name: 工具名称
arguments: 工具参数
Returns:
工具执行结果
"""
result = await self.session.call_tool(name, arguments)
return result
async def close(self):
"""关闭连接"""
if self.session:
await self.session.__aexit__(None, None, None)
if self.client:
await self.client.__aexit__(None, None, None)
print("🔌 已断开连接")
async def main():
"""主函数 - Client 使用示例"""
client = MCPPythonClient()
try:
# 1. 连接到 Server
await client.connect("server.py")
# 2. 获取工具列表
print("\n📋 可用工具列表:")
tools = await client.list_tools()
for i, tool in enumerate(tools, 1):
print(f" {i}. {tool.name}: {tool.description}")
# 3. 调用 calculator 工具
print("\n🔧 测试调用 calculator 工具:")
calc_result = await client.call_tool("calculator", {
"expression": "15 * 8 - 30"
})
for content in calc_result.content:
print(f"结果:{content.text}")
# 4. 调用随机数生成工具
print("\n🔧 测试调用 generate_random 工具:")
random_result = await client.call_tool("generate_random", {
"min": 1,
"max": 100,
"count": 5
})
for content in random_result.content:
print(f"结果:{content.text}")
except Exception as e:
print(f"❌ 错误:{e}")
finally:
# 5. 关闭连接
await client.close()
if __name__ == "__main__":
# 运行异步主函数
asyncio.run(main())
Python vs TypeScript:
• Python SDK 使用异步编程(async/await)
• TypeScript SDK 可选择 Promise 或 async/await
• 两者功能等价,根据你的技术栈选择
• Python SDK 使用异步编程(async/await)
• TypeScript SDK 可选择 Promise 或 async/await
• 两者功能等价,根据你的技术栈选择
六、与 LLM 集成
MCP 的真正威力在于与 LLM(大语言模型)的结合。下面展示如何将 Tools 列表传递给 LLM,并让 AI 决定何时调用工具。
集成流程示意
┌─────────────────────────────────────────────────────────────┐
│ Client 应用程序 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 用户输入 │───►│ LLM 处理 │───►│ 展示结果 │ │
│ └──────────────┘ └──────┬───────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ 需要调用工具? │
│ / \ │
│ 是 否 │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ 直接回答 │
│ │ MCP Client │ │
│ │ callTool() │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ MCP Server │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
与 OpenAI API 集成示例
// llm-client.js - 与 LLM 集成的 MCP Client
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import OpenAI from 'openai';
import readline from 'readline';
class LLMMCPClient {
constructor() {
this.mcpClient = null;
this.openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
this.tools = []; // 存储 MCP Tools
}
/**
* 初始化:连接 MCP Server 并获取 Tools
*/
async initialize(serverPath) {
// 连接 MCP Server
this.mcpClient = new Client({
name: 'llm-mcp-client',
version: '1.0.0',
});
const transport = new StdioClientTransport({
command: 'node',
args: [serverPath],
});
await this.mcpClient.connect(transport);
console.log('✅ 已连接到 MCP Server');
// 获取 Tools 列表并转换为 OpenAI function calling 格式
const response = await this.mcpClient.listTools();
this.tools = this.convertToOpenAIFunctions(response.tools);
console.log(`📋 已加载 ${this.tools.length} 个工具`);
}
/**
* 将 MCP Tools 转换为 OpenAI function 格式
*/
convertToOpenAIFunctions(mcpTools) {
return mcpTools.map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
}));
}
/**
* 处理用户消息,与 LLM 交互
*/
async processMessage(userMessage) {
const messages = [
{
role: 'system',
content: '你是一个智能助手。当需要执行计算、查询时间或生成随机数时,请使用提供的工具。'
},
{
role: 'user',
content: userMessage,
},
];
// 第一步:发送消息给 LLM,附带可用工具
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: messages,
tools: this.tools,
tool_choice: 'auto', // 让 LLM 自动决定是否调用工具
});
const responseMessage = response.choices[0].message;
// 检查 LLM 是否决定调用工具
if (responseMessage.tool_calls) {
console.log('🤖 LLM 决定调用工具');
// 执行工具调用
const toolResults = [];
for (const toolCall of responseMessage.tool_calls) {
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments);
console.log(` 调用: ${functionName}(${JSON.stringify(functionArgs)})`);
// 调用 MCP Tool
const result = await this.mcpClient.callTool({
name: functionName,
arguments: functionArgs,
});
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
content: result.content[0].text,
});
}
// 将工具结果返回给 LLM
messages.push(responseMessage); // 添加 LLM 的 tool_calls 消息
messages.push(...toolResults); // 添加工具执行结果
// 第二步:让 LLM 根据工具结果生成最终回答
const finalResponse = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: messages,
});
return finalResponse.choices[0].message.content;
}
// LLM 直接回答了,没有调用工具
return responseMessage.content;
}
/**
* 启动交互式对话
*/
async startChat() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
console.log('\n🤖 LLM MCP Client 已启动');
console.log('可用命令:');
console.log(' - 直接输入问题与 AI 对话');
console.log(' - 输入 "quit" 退出\n');
const askQuestion = () => {
rl.question('你: ', async (input) => {
if (input.toLowerCase() === 'quit') {
console.log('再见!');
rl.close();
return;
}
try {
const response = await this.processMessage(input);
console.log('\nAI: ', response, '\n');
} catch (error) {
console.error('错误:', error.message);
}
askQuestion();
});
};
askQuestion();
}
}
// ===== 运行 =====
async function main() {
const client = new LLMMCPClient();
try {
// 初始化并连接 Server
await client.initialize('./server.js');
// 启动对话
await client.startChat();
} catch (error) {
console.error('启动失败:', error);
}
}
main();
对话示例:
你: 现在几点了?
🤖 LLM 决定调用工具
调用: get_current_time({"format": "full"})
AI: 当前时间是 2026年3月27日 星期五 09:30:15 CST
你: 帮我计算 123 乘以 456
🤖 LLM 决定调用工具
调用: calculator({"expression": "123 * 456"})
AI: 123 乘以 456 等于 56,088
你: 生成一个 1-100 的随机数
🤖 LLM 决定调用工具
调用: generate_random({"min": 1, "max": 100})
AI: 为您生成的随机数是 42
七、完整示例:命令行助手
现在让我们开发一个完整的命令行助手,整合前面学习的所有知识:
// cli-assistant.js - 完整的命令行 AI 助手
#!/usr/bin/env node
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import readline from 'readline';
import chalk from 'chalk'; // 用于彩色输出
/**
* 命令行 AI 助手
* 支持 MCP Server 工具调用、对话历史、错误处理
*/
class CLIAssistant {
constructor(config = {}) {
this.config = {
serverPath: config.serverPath || './server.js',
serverCommand: config.serverCommand || 'node',
showToolCalls: config.showToolCalls !== false, // 默认显示工具调用
...config,
};
this.client = null;
this.tools = [];
this.conversationHistory = []; // 对话历史
this.stats = {
toolCalls: 0,
messages: 0,
};
}
/**
* 初始化:连接 Server
*/
async initialize() {
console.log(chalk.blue('🔌 正在连接到 MCP Server...'));
try {
// 创建 Client
this.client = new Client({
name: 'cli-assistant',
version: '1.0.0',
});
// 创建传输层
const transport = new StdioClientTransport({
command: this.config.serverCommand,
args: [this.config.serverPath],
});
// 连接(带超时)
const connectPromise = this.client.connect(transport);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('连接超时(10秒)')), 10000);
});
await Promise.race([connectPromise, timeoutPromise]);
// 获取工具列表
const toolsResponse = await this.client.listTools();
this.tools = toolsResponse.tools;
console.log(chalk.green(`✅ 连接成功!已加载 ${this.tools.length} 个工具`));
this.printTools();
} catch (error) {
throw new Error(`初始化失败: ${error.message}`);
}
}
/**
* 显示可用工具
*/
printTools() {
console.log(chalk.cyan('\n📋 可用工具:'));
this.tools.forEach((tool, i) => {
console.log(chalk.gray(` ${i + 1}. ${chalk.white(tool.name)}`));
console.log(chalk.gray(` ${tool.description.slice(0, 60)}...`));
});
console.log('');
}
/**
* 解析用户输入,判断是否需要调用工具
* 简化版:使用关键词匹配
*/
async processInput(input) {
// 添加到历史
this.conversationHistory.push({ role: 'user', content: input });
this.stats.messages++;
// 特殊命令
if (input.startsWith('/')) {
return this.handleCommand(input);
}
// 根据关键词判断调用哪个工具
const toolCall = this.detectToolCall(input);
if (toolCall) {
return this.executeTool(toolCall);
}
// 没有匹配的工具,返回默认回复
return '我不太确定如何帮助你。你可以尝试询问时间、计算数学表达式或生成随机数。';
}
/**
* 检测用户输入是否匹配某个工具
*/
detectToolCall(input) {
const lowerInput = input.toLowerCase();
// 计算器:包含数字和运算符
if (/[\d+\-*/].*/.test(input) &&
(lowerInput.includes('计算') || lowerInput.includes('等于') || lowerInput.includes('多少'))) {
// 提取数学表达式
const match = input.match(/([\d+\-*/().\s]+)/);
if (match) {
return {
name: 'calculator',
args: { expression: match[1].replace(/\s/g, '') },
};
}
}
// 时间查询
if (lowerInput.includes('时间') || lowerInput.includes('几点') || lowerInput.includes('日期')) {
const format = lowerInput.includes('日期') ? 'date' :
lowerInput.includes('时间') && !lowerInput.includes('日期') ? 'time' : 'full';
return {
name: 'get_current_time',
args: { format },
};
}
// 随机数
if (lowerInput.includes('随机') || lowerInput.includes('随机数')) {
const rangeMatch = input.match(/(\d+)\s*到\s*(\d+)/);
if (rangeMatch) {
return {
name: 'generate_random',
args: { min: parseInt(rangeMatch[1]), max: parseInt(rangeMatch[2]) },
};
}
return {
name: 'generate_random',
args: { min: 1, max: 100 },
};
}
return null;
}
/**
* 执行工具调用
*/
async executeTool(toolCall) {
if (this.config.showToolCalls) {
console.log(chalk.yellow(`\n🔧 正在调用: ${toolCall.name}`));
console.log(chalk.gray(` 参数: ${JSON.stringify(toolCall.args)}`));
}
try {
const result = await this.client.callTool({
name: toolCall.name,
arguments: toolCall.args,
});
this.stats.toolCalls++;
// 提取文本内容
const text = result.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
return text;
} catch (error) {
return `工具调用失败: ${error.message}`;
}
}
/**
* 处理特殊命令
*/
handleCommand(input) {
const parts = input.slice(1).split(' ');
const cmd = parts[0];
switch (cmd) {
case 'help':
return `可用命令:
/help - 显示帮助
/tools - 显示工具列表
/stats - 显示统计信息
/clear - 清空对话历史
/quit - 退出程序`;
case 'tools':
this.printTools();
return '';
case 'stats':
return `统计信息:
对话消息: ${this.stats.messages}
工具调用: ${this.stats.toolCalls}
可用工具: ${this.tools.length}`;
case 'clear':
this.conversationHistory = [];
return '对话历史已清空';
case 'quit':
return null; // 返回 null 表示退出
default:
return `未知命令: ${cmd},输入 /help 查看可用命令`;
}
}
/**
* 启动交互式会话
*/
async start() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.cyan('你> '),
});
console.log(chalk.green.bold('\n🤖 MCP CLI 助手已启动!'));
console.log(chalk.gray('输入 /help 查看命令,输入 /quit 退出\n'));
rl.prompt();
rl.on('line', async (input) => {
const trimmed = input.trim();
if (!trimmed) {
rl.prompt();
return;
}
try {
const response = await this.processInput(trimmed);
if (response === null) {
console.log(chalk.yellow('👋 再见!'));
rl.close();
return;
}
if (response) {
console.log(chalk.green('AI> ') + response + '\n');
}
} catch (error) {
console.error(chalk.red('错误: ') + error.message);
}
rl.prompt();
});
return new Promise((resolve) => {
rl.on('close', resolve);
});
}
/**
* 关闭连接
*/
async close() {
if (this.client) {
// Client 没有显式的 close 方法,依赖 transport 关闭
console.log(chalk.gray('\n🔌 断开连接'));
}
}
}
// ===== 主程序 =====
async function main() {
const assistant = new CLIAssistant({
serverPath: './server.js',
showToolCalls: true,
});
try {
await assistant.initialize();
await assistant.start();
} catch (error) {
console.error(chalk.red('错误: ') + error.message);
process.exit(1);
} finally {
await assistant.close();
}
}
main();
运行效果
$ node cli-assistant.js
🔌 正在连接到 MCP Server...
✅ 连接成功!已加载 3 个工具
📋 可用工具:
1. calculator
执行数学计算,支持:基础运算、幂运算、三角函数...
2. get_current_time
获取当前日期和时间信息...
3. generate_random
生成随机数...
🤖 MCP CLI 助手已启动!
输入 /help 查看命令,输入 /quit 退出
你> 现在几点了?
🔧 正在调用: get_current_time
参数: {"format":"full"}
AI> 当前时间:2026年3月27日 星期五 09:30:15 GMT+8(时区:Asia/Shanghai)
你> 计算 100 + 200 等于多少
🔧 正在调用: calculator
参数: {"expression":"100+200"}
AI> 计算结果:100+200 = 300
你> 生成 1 到 100 的随机数
🔧 正在调用: generate_random
参数: {"min":1,"max":100}
AI> 生成的随机数:42
你> /stats
AI> 统计信息:
对话消息: 4
工具调用: 3
可用工具: 3
你> /quit
👋 再见!
八、错误处理与重试
在实际应用中,需要处理各种错误情况。以下是完善的错误处理策略:
常见错误类型
// error-handling.js - 错误处理示例
class ResilientMCPClient {
constructor(config = {}) {
this.config = {
maxRetries: config.maxRetries || 3,
retryDelay: config.retryDelay || 1000, // 毫秒
timeout: config.timeout || 10000, // 毫秒
...config,
};
this.client = null;
this.retryCount = 0;
}
/**
* 带重试的连接
*/
async connectWithRetry(serverPath) {
while (this.retryCount < this.config.maxRetries) {
try {
await this.connect(serverPath);
console.log('✅ 连接成功');
return;
} catch (error) {
this.retryCount++;
console.error(`❌ 连接失败 (${this.retryCount}/${this.config.maxRetries}): ${error.message}`);
if (this.retryCount < this.config.maxRetries) {
console.log(`⏳ ${this.config.retryDelay}ms 后重试...`);
await this.sleep(this.config.retryDelay);
}
}
}
throw new Error(`连接失败,已重试 ${this.config.maxRetries} 次`);
}
/**
* 建立连接
*/
async connect(serverPath) {
this.client = new Client({
name: 'resilient-client',
version: '1.0.0',
});
const transport = new StdioClientTransport({
command: 'node',
args: [serverPath],
});
// 使用 Promise.race 实现超时
const connectPromise = this.client.connect(transport);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`连接超时 (${this.config.timeout}ms)`));
}, this.config.timeout);
});
await Promise.race([connectPromise, timeoutPromise]);
this.retryCount = 0; // 重置重试计数
}
/**
* 安全的工具调用
*/
async callToolSafely(name, args) {
// 参数验证
if (!name || typeof name !== 'string') {
throw new Error('工具名称无效');
}
try {
const result = await this.client.callTool({ name, arguments: args });
// 检查结果是否包含错误
if (result.isError) {
const errorText = result.content
.filter(c => c.type === 'text')
.map(c => c.text)
.join('\n');
throw new Error(`工具执行错误: ${errorText}`);
}
return result;
} catch (error) {
// 分类错误
if (error.message.includes('Unknown tool')) {
throw new Error(`工具 "${name}" 不存在,请检查工具名称`);
}
if (error.message.includes('timeout')) {
throw new Error(`调用超时,请检查 Server 是否响应`);
}
throw error;
}
}
/**
* 健康检查
*/
async healthCheck() {
try {
await this.client.listTools();
return { status: 'healthy', timestamp: new Date().toISOString() };
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString(),
};
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
错误处理最佳实践
| 错误类型 | 处理策略 | 示例代码 |
|---|---|---|
| 连接失败 | 重试机制 + 指数退避 | 最多重试 3 次,间隔递增 |
| 连接超时 | 设置超时限制 + 友好提示 | 10 秒超时,提示检查 Server |
| 工具不存在 | 获取工具列表验证 + 错误提示 | 提示可用的工具名称 |
| 参数错误 | 参数校验 + Schema 验证 | 调用前检查必需参数 |
| Server 崩溃 | 异常捕获 + 自动重连 | 捕获异常,尝试重新连接 |
生产环境注意:
• 添加详细的日志记录
• 实现监控和告警
• 限制重试次数,避免无限循环
• 为长时间运行的工具设置超时
• 添加详细的日志记录
• 实现监控和告警
• 限制重试次数,避免无限循环
• 为长时间运行的工具设置超时
本章小结
本章全面介绍了 MCP Client 的开发:
- MCP Client 概念:连接 Server、发现能力、调用工具的角色
- TypeScript Client:使用
@modelcontextprotocol/sdk开发 - Python Client:使用
mcp包开发 - 调用流程:listTools() → LLM 决策 → callTool() → 处理结果
- LLM 集成:将 Tools 转换为 Function Calling 格式
- 错误处理:重试、超时、参数验证等策略
学习路径建议:
1. 先实现基础的 Client,能够连接 Server 并调用工具
2. 再集成 LLM,让 AI 自主决定何时调用工具
3. 最后添加错误处理和重试机制,提升稳定性
1. 先实现基础的 Client,能够连接 Server 并调用工具
2. 再集成 LLM,让 AI 自主决定何时调用工具
3. 最后添加错误处理和重试机制,提升稳定性
至此,你已经掌握了 MCP Server 和 Client 的完整开发技能。下一章将学习 MCP 的高级特性与最佳实践,包括认证授权、性能优化、安全考虑等进阶话题。