通过服务器使 LLM 执行操作
工具(Tools)是 模型上下文协议(MCP,Model Context Protocol) 中的一种强大原语,它使服务器能够向客户端暴露可执行的功能。通过工具,LLM(大语言模型)可以与外部系统交互、执行计算,并在现实世界中执行操作。
工具的设计原则是 模型控制(model-controlled),即工具由服务器暴露给客户端,AI 模型可以自动调用它们(但通常需要人工批准)。
概述
MCP 中的工具允许服务器暴露可执行的函数,这些函数可以被客户端调用,并用于让 LLM 执行动作。工具的关键特点包括:
- 发现(Discovery):客户端可以通过
tools/list
端点获取可用工具的列表。 - 调用(Invocation):工具通过
tools/call
端点调用,服务器执行请求的操作并返回结果。 - 灵活性(Flexibility):工具可以从简单的计算扩展到复杂的 API 交互。
与 资源(resources) 类似,工具也有唯一的名称和描述,以指导其使用。但与资源不同的是,工具代表动态操作,可能会修改状态或与外部系统交互。
工具定义结构
每个工具都按照以下结构定义:
{
name: string; // 工具的唯一标识符
description?: string; // 人类可读的描述
inputSchema: { // 工具参数的 JSON Schema
type: "object",
properties: { ... } // 工具特定的参数
}
}
实现工具
以下是 MCP 服务器中实现基本工具的示例:
TypeScript 示例
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
// 定义可用工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [{
name: "calculate_sum",
description: "计算两个数的和",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
}]
};
});
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "calculate_sum") {
const { a, b } = request.params.arguments;
return {
content: [
{
type: "text",
text: String(a + b)
}
]
};
}
throw new Error("工具未找到");
});
示例工具模式
服务器可以提供多种类型的工具,例如:
1. 系统操作(System operations)
与本地系统交互的工具:
{
name: "execute_command",
description: "运行 Shell 命令",
inputSchema: {
type: "object",
properties: {
command: { type: "string" },
args: { type: "array", items: { type: "string" } }
}
}
}
2. API 集成(API integrations)
封装外部 API 的工具:
{
name: "github_create_issue",
description: "创建 GitHub Issue",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
labels: { type: "array", items: { type: "string" } }
}
}
}
3. 数据处理(Data processing)
处理或分析数据的工具:
{
name: "analyze_csv",
description: "分析 CSV 文件",
inputSchema: {
type: "object",
properties: {
filepath: { type: "string" },
operations: {
type: "array",
items: {
enum: ["sum", "average", "count"]
}
}
}
}
}
最佳实践
实现工具时应遵循以下最佳实践:
- 提供清晰、描述性的名称和描述
- 使用详细的 JSON Schema 定义参数
- 在工具描述中包含示例,帮助模型正确调用
- 实现适当的错误处理和参数验证
- 为长时间运行的操作提供进度报告
- 保持工具的功能专注且原子化(单一职责)
- 记录返回值的结构
- 实现适当的超时处理
- 对资源密集型操作进行速率限制
- 记录工具的使用情况,以便调试和监控
安全考虑
在暴露工具时,需要注意以下安全问题:
1. 输入验证(Input validation)
- 确保所有参数都符合 JSON Schema 规范。
- 对文件路径和系统命令进行清理和验证。
- 检查 URL 和外部标识符的合法性。
- 限制参数的大小和范围,防止滥用。
- 防止命令注入等安全漏洞。
2. 访问控制(Access control)
- 在必要时实施身份验证(Authentication)。
- 采用适当的授权检查(Authorization)。
- 审计工具的使用情况。
- 限制请求速率,防止滥用。
- 监控并检测异常使用模式。
3. 错误处理(Error handling)
- 不要向客户端暴露内部错误信息,防止信息泄露。
- 记录安全相关的错误信息,以便审计和调查。
- 适当处理超时,避免资源泄露或阻塞。
- 在发生错误时清理资源,避免系统资源耗尽。
- 验证返回值,确保其符合预期格式。
工具发现与更新
MCP 支持动态工具发现:
- 客户端可以随时查询可用工具列表(
tools/list
)。 - 服务器可以通过
notifications/tools/list_changed
通知客户端工具列表变更。 - 工具可以在运行时动态添加或删除。
- 工具定义可以更新(但应谨慎进行,以确保兼容性)。
错误处理
工具的错误应在返回结果对象中报告,而不是作为 MCP 协议级别的错误。这样,LLM 可以识别错误并尝试进行恢复或请求人工干预。
当工具发生错误时,应:
- 在返回结果中设置
isError: true
。 - 在
content
数组中包含错误详情。
TypeScript 示例
try {
// 执行工具操作
const result = performOperation();
return {
content: [
{
type: "text",
text: `操作成功: ${result}`
}
]
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `错误: ${error.message}`
}
]
};
}
这样,LLM 可以检测到错误,并尝试采取纠正措施或请求人工协助。
工具测试
要确保 MCP 工具的稳定性和安全性,需要进行以下测试:
- 功能测试(Functional testing):验证工具在输入合法时能正确执行,在输入不合法时能正确处理。
- 集成测试(Integration testing):测试工具与外部系统的交互,包括真实 API 调用和模拟测试。
- 安全测试(Security testing):检查身份验证、权限控制、输入清理和速率限制等安全措施。
- 性能测试(Performance testing):评估工具在高负载下的表现,包括超时处理和资源清理。
- 错误处理测试(Error handling testing):确保工具在异常情况下能够正确报告错误并清理资源。