工具
工具是模型上下文协议(MCP)中的一个强大原语,使服务器能够向客户端暴露可执行的功能。通过工具,LLM可以与外部系统交互、执行计算并在现实世界中采取行动。
备注
工具设计为模型控制,这意味着工具从服务器暴露给客户端,目的是让AI模型能够自动调用它们(需要人在回路中授予批准)。
概述
MCP中的工具允许服务器暴露可执行函数,这些函数可以被客户端调用并被LLM用来执行操作。工具的关键方面包括:
- 发现:客户端可以通过
tools/list
端点列出可用工具 - 调用:使用
tools/call
端点调用工具,服务器执行请求的 操作并返回结果 - 灵活性:工具可以从简单的计算到复杂的API交互
与资源类似,工具由唯一的名称标识,并可以包含描述来指导其使用。然而,与资源不同,工具代表可以修改状态或与外部系统交互的动态操作。
工具定义结构
每个工具的定义结构如下:
{
name: string; // 工具的唯一标识符
description?: string; // 人类可读的描述
inputSchema: { // 工具参数的JSON Schema
type: "object",
properties: { ... } // 工具特定的参数
}
}
实现工具
这是在MCP服务器中实现基本工具的示例:
- TypeScript
- Python
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("工具未找到");
});
app = Server("example-server")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="calculate_sum",
description="将两个数字相加",
inputSchema={
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
}
)
]
@app.call_tool()
async def call_tool(
name: str,
arguments: dict
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
if name == "calculate_sum":
a = arguments["a"]
b = arguments["b"]
result = a + b
return [types.TextContent(type="text", text=str(result))]
raise ValueError(f"工具未找到: {name}")
工具模式示例
以下是一些服务器可以提供的工具类型示例:
系统操作
与本地系统交互的工具:
{
name: "execute_command",
description: "运行shell命令",
inputSchema: {
type: "object",
properties: {
command: { type: "string" },
args: { type: "array", items: { type: "string" } }
}
}
}
API集成
包装外部API的工具:
{
name: "github_create_issue",
description: "创建GitHub问题",
inputSchema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
labels: { type: "array", items: { type: "string" } }
}
}
}
数据处理
转换或分析数据的工具:
{
name: "analyze_csv",
description: "分析CSV文件",
inputSchema: {
type: "object",
properties: {
filepath: { type: "string" },
operations: {
type: "array",
items: {
enum: ["sum", "average", "count"]
}
}
}
}
}
最佳实践
在实现工具时:
- 提供清晰、描述性的名称和描述
- 使用详细的JSON Schema定义参数
- 在工具描述中包含示例以演示模型应如何使用它们
- 实现适当的错误处理和验证
- 对长时间运行的操作使用进度报告
- 保持工具操作专注和原子性
- 记录预期的返回值结构
- 实现适当的超时
- 考虑对资源密集型操作进行速率限制
- 记录工具使用情况以便调试和监控
安全考虑
在暴露工具时:
输入验证
- 根据schema验证所有参数
- 清理文件路径和系统命令
- 验证URL和外部标识符
- 检查参数大小和范围
- 防止命令注入
访问控制
- 在需要时实现身份验证
- 使用适当的授权检查
- 审计工具使用情况
- 限制请求速率
- 监控滥用情况
错误处理
- 不向客户端暴露内部错误
- 记录与安全相关的错误
- 适当处理超时
- 在错误后清理资源
- 验证返回值
工具发现和更新
MCP支持动态工具发现:
- 客户端可以随时列出可用工具
- 服务器可以使用
notifications/tools/list_changed
通知客户端工具变更 - 工具可以在运行时添加或删除
- 工具定义可以更新(但应谨慎进行)
错误处理
工具错误应在结果对象中报告,而不是作为MCP协议级错误。这允许LLM看到并可能处理错误。当工具遇到错误时:
- 在结果中将
isError
设置为true
- 在
content
数组中包含错误详情
这是工具正确错误处理的示例:
- TypeScript
- Python
// 处理工具执行
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "analyze_csv") {
try {
const { filepath, operations } = request.params.arguments;
// 验证文件存在
if (!await fileExists(filepath)) {
return {
isError: true,
content: [
{
type: "text",
text: `文件未找到: ${filepath}`
}
]
};
}
// 执行分析
const results = await analyzeCSV(filepath, operations);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2)
}
]
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `分析CSV文件时出错: ${error.message}`
}
]
};
}
}
throw new Error("工具未找到");
});
@app.call_tool()
async def call_tool(
name: str,
arguments: dict
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
if name == "analyze_csv":
try:
filepath = arguments["filepath"]
operations = arguments["operations"]
# 验证文件存在
if not await file_exists(filepath):
return [
types.TextContent(
type="text",
text=f"文件未找到: {filepath}"
)
]
# 执行分析
results = await analyze_csv(filepath, operations)
return [
types.TextContent(
type="text",
text=json.dumps(results, indent=2)
)
]
except Exception as error:
return [
types.TextContent(
type="text",
text=f"分析CSV文件时出错: {str(error)}"
)
]
raise ValueError(f"工具未找到: {name}")