【翻译、转载】MCP 工具 (Tools)
原文:https://modelcontextprotocol.io/docs/concepts/tools
工具 (Tools)
使 LLM 能够通过您的服务器执行操作
工具是模型上下文协议 (MCP) 中一个强大的原语 (primitive),它使服务器能够向客户端暴露可执行的功能。通过工具,LLM 可以与外部系统交互、执行计算,并在现实世界中采取行动。
工具被设计为**模型控制 (model-controlled)**,这意味着工具从服务器暴露给客户端,意图是让 AI 模型能够自动调用它们(通常需要人工介入以授予批准)。概述
MCP 中的工具允许服务器暴露可执行的函数,客户端可以调用这些函数,LLM 可以使用它们来执行操作。工具的关键方面包括:
- 发现 (Discovery):客户端可以通过
tools/list
端点列出可用的工具。 - 调用 (Invocation):使用
tools/call
端点调用工具,服务器执行请求的操作并返回结果。 - 灵活性 (Flexibility):工具的范围可以从简单的计算到复杂的 API 交互。
与 资源 (resources) 类似,工具由唯一的名称标识,并可以包含描述以指导其使用。然而,与资源不同,工具代表可以修改状态或与外部系统交互的动态操作。
工具定义结构
每个工具都使用以下结构进行定义:
{name: string; // 工具的唯一标识符description?: string; // 人类可读的描述inputSchema: { // 工具参数的 JSON Schematype: "object",properties: { ... } // 工具特定的参数},annotations?: { // 关于工具行为的可选提示title?: string; // 工具的人类可读标题readOnlyHint?: boolean; // 若为 true,表示工具不修改其环境destructiveHint?: boolean; // 若为 true,表示工具可能执行破坏性更新idempotentHint?: boolean; // 若为 true,表示使用相同参数重复调用不会产生额外效果openWorldHint?: boolean; // 若为 true,表示工具与外部实体交互}
}
实现工具
这是一个在 MCP 服务器中实现基本工具的示例:
app = Server("example-server")@app.list_tools()
async def list_tools() -> list[types.Tool]:return [types.Tool(name="calculate_sum",description="Add two numbers together",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 + breturn [types.TextContent(type="text", text=str(result))]raise ValueError(f"Tool not found: {name}")
示例工具模式
以下是一些服务器可以提供的工具类型的示例:
系统操作
与本地系统交互的工具:
{name: "execute_command",description: "运行一个 shell 命令", // Run a shell commandinputSchema: {type: "object",properties: {command: { type: "string" }, // 命令args: { type: "array", items: { type: "string" } } // 参数列表}}
}
API 集成
包装外部 API 的工具:
{name: "github_create_issue",description: "创建一个 GitHub issue", // Create a GitHub issueinputSchema: {type: "object",properties: {title: { type: "string" }, // 标题body: { type: "string" }, // 内容labels: { type: "array", items: { type: "string" } } // 标签}}
}
数据处理
转换或分析数据的工具:
{name: "analyze_csv",description: "分析一个 CSV 文件", // Analyze a CSV fileinputSchema: {type: "object",properties: {filepath: { type: "string" }, // 文件路径operations: { // 要执行的操作type: "array",items: {enum: ["sum", "average", "count"] // 例如求和、平均值、计数}}}}
}
最佳实践
在实现工具时:
- 提供清晰、描述性的名称和描述
- 对参数使用详细的 JSON Schema 定义
- 在工具描述中包含示例,以演示模型应如何使用它们
- 实现正确的错误处理和验证
- 对长时间操作使用进度报告
- 保持工具操作的专注性和原子性
- 记录预期的返回值结构
- 实现适当的超时机制
- 考虑对资源密集型操作进行速率限制
- 记录工具使用情况以供调试和监控
安全注意事项
在暴露工具时:
输入验证
- 根据模式验证所有参数
- 净化文件路径和系统命令
- 验证 URL 和外部标识符
- 检查参数大小和范围
- 防止命令注入
访问控制
- 在需要时实施身份验证
- 使用适当的授权检查
- 审计工具使用情况
- 对请求进行速率限制
- 监控滥用行为
错误处理
- 不要向客户端暴露内部错误
- 记录与安全相关的错误
- 适当地处理超时
- 错误后清理资源
- 验证返回值
工具发现与更新
MCP 支持动态工具发现:
- 客户端可以随时列出可用工具
- 服务器可以使用
notifications/tools/list_changed
通知客户端工具发生变化 - 可以在运行时添加或删除工具
- 可以更新工具定义(但这应谨慎进行)
错误处理
工具错误应在结果对象内报告,而不是作为 MCP 协议级别的错误。这允许 LLM 看到并可能处理该错误。当工具遇到错误时:
- 在结果中将
isError
设置为true
- 在
content
数组中包含错误详细信息
这是工具正确错误处理的示例:
try:# Tool operationresult = perform_operation()return types.CallToolResult(content=[types.TextContent(type="text",text=f"Operation successful: {result}")])
except Exception as error:return types.CallToolResult(isError=True,content=[types.TextContent(type="text",text=f"Error: {str(error)}")])
这种方法允许 LLM 看到发生了错误,并可能采取纠正措施或请求人工干预。
工具注解 (Tool annotations)
工具注解提供了有关工具行为的附加元数据,帮助客户端理解如何呈现和管理工具。这些注解是描述工具性质和影响的提示,但不应依赖它们来进行安全决策。
工具注解的目的
工具注解有几个关键目的:
- 提供特定于用户体验 (UX) 的信息,而不影响模型上下文
- 帮助客户端适当地分类和呈现工具
- 传达有关工具潜在副作用的信息
- 协助开发用于工具批准的直观界面
可用的工具注解
MCP 规范定义了以下工具注解:
注解 (Annotation) | 类型 (Type) | 默认值 (Default) | 描述 (Description) |
---|---|---|---|
title | string | - | 工具的人类可读标题,对 UI 显示很有用 |
readOnlyHint | boolean | false | 若为 true,表示该工具不修改其环境 |
destructiveHint | boolean | true | 若为 true,表示该工具可能执行破坏性更新(仅当 readOnlyHint 为 false 时有意义) |
idempotentHint | boolean | false | 若为 true,表示使用相同参数重复调用该工具不会产生额外效果(仅当 readOnlyHint 为 false 时有意义) |
openWorldHint | boolean | true | 若为 true,表示该工具可能与外部实体的“开放世界”交互 |
使用示例
以下是如何为不同场景定义带有注解的工具:
// 一个只读的搜索工具
{name: "web_search",description: "在网上搜索信息", // Search the web for informationinputSchema: { /* ... */ },annotations: {title: "网页搜索", // Web SearchreadOnlyHint: true, // 只读openWorldHint: true // 与开放世界交互}
}// 一个破坏性的文件删除工具
{name: "delete_file",description: "从文件系统删除文件", // Delete a file from the filesysteminputSchema: { /* ... */ },annotations: {title: "删除文件", // Delete FilereadOnlyHint: false, // 非只读destructiveHint: true, // 具有破坏性idempotentHint: true, // 幂等(删除同一个文件多次效果相同)openWorldHint: false // 不与开放世界交互(操作本地文件系统)}
}// 一个非破坏性的数据库记录创建工具
{name: "create_record",description: "在数据库中创建新记录", // Create a new record in the databaseinputSchema: { /* ... */ },annotations: {title: "创建数据库记录", // Create Database RecordreadOnlyHint: false, // 非只读(修改数据库状态)destructiveHint: false, // 非破坏性(只是添加数据)idempotentHint: false, // 非幂等(多次调用会创建多条记录)openWorldHint: false // 不与开放世界交互(操作内部数据库)}
}
在服务器实现中集成注解
```typescript server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [{ name: "calculate_sum", description: "将两个数字相加", inputSchema: { /* ... */ }, annotations: { // 添加注解 title: "计算总和", // Calculate Sum readOnlyHint: true, openWorldHint: false } }] }; }); ``` ```python # 假设 FastMCP 已导入 from mcp.server.fastmcp import FastMCPmcp = FastMCP("example-server")# 在装饰器中添加注解
@mcp.tool(annotations={"title": "计算总和", # Calculate Sum"readOnlyHint": True,"openWorldHint": False}
)
async def calculate_sum(a: float, b: float) -> str:"""将两个数字相加。Args:a: 第一个加数b: 第二个加数"""result = a + breturn str(result)
```
工具注解的最佳实践
- 准确描述副作用:清楚地表明工具是否修改其环境,以及这些修改是否具有破坏性。
- 使用描述性标题:提供用户友好的标题,清楚地描述工具的用途。
- 正确指示幂等性:仅当使用相同参数重复调用确实没有额外效果时,才将工具标记为幂等。
- 设置适当的开放/封闭世界提示:指明工具是与封闭系统(如数据库)还是开放系统(如网络)交互。
- 记住注解是提示:ToolAnnotations 中的所有属性都是提示,不能保证对工具行为提供完全忠实的描述。客户端绝不应仅基于注解做出安全关键决策。
测试工具
MCP 工具的全面测试策略应涵盖:
- 功能测试:验证工具使用有效输入时执行正确,并能适当地处理无效输入。
- 集成测试:使用真实和模拟的依赖项测试工具与外部系统的交互。
- 安全测试:验证身份验证、授权、输入净化和速率限制。
- 性能测试:检查负载下的行为、超时处理和资源清理。
- 错误处理:确保工具通过 MCP 协议正确报告错误并清理资源。