跳到主要内容

工具

工具是模型上下文协议(MCP)中的一个强大原语,使服务器能够向客户端暴露可执行的功能。通过工具,LLM可以与外部系统交互、执行计算并在现实世界中采取行动。

备注

工具设计为模型控制,这意味着工具从服务器暴露给客户端,目的是让AI模型能够自动调用它们(需要人在回路中授予批准)。

概述

MCP中的工具允许服务器暴露可执行函数,这些函数可以被客户端调用并被LLM用来执行操作。工具的关键方面包括:

  • 发现:客户端可以通过tools/list端点列出可用工具
  • 调用:使用tools/call端点调用工具,服务器执行请求的操作并返回结果
  • 灵活性:工具可以从简单的计算到复杂的API交互

资源类似,工具由唯一的名称标识,并可以包含描述来指导其使用。然而,与资源不同,工具代表可以修改状态或与外部系统交互的动态操作。

工具定义结构

每个工具的定义结构如下:

{
  name: string;          // 工具的唯一标识符
  description?: string;  // 人类可读的描述
  inputSchema: {         // 工具参数的JSON Schema
    type: "object",
    properties: { ... }  // 工具特定的参数
  }
}

实现工具

这是在MCP服务器中实现基本工具的示例:

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("工具未找到");
});

工具模式示例

以下是一些服务器可以提供的工具类型示例:

系统操作

与本地系统交互的工具:

{
  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"]
        }
      }
    }
  }
}

最佳实践

在实现工具时:

  1. 提供清晰、描述性的名称和描述
  2. 使用详细的JSON Schema定义参数
  3. 在工具描述中包含示例以演示模型应如何使用它们
  4. 实现适当的错误处理和验证
  5. 对长时间运行的操作使用进度报告
  6. 保持工具操作专注和原子性
  7. 记录预期的返回值结构
  8. 实现适当的超时
  9. 考虑对资源密集型操作进行速率限制
  10. 记录工具使用情况以便调试和监控

安全考虑

在暴露工具时:

输入验证

  • 根据schema验证所有参数
  • 清理文件路径和系统命令
  • 验证URL和外部标识符
  • 检查参数大小和范围
  • 防止命令注入

访问控制

  • 在需要时实现身份验证
  • 使用适当的授权检查
  • 审计工具使用情况
  • 限制请求速率
  • 监控滥用情况

错误处理

  • 不向客户端暴露内部错误
  • 记录与安全相关的错误
  • 适当处理超时
  • 在错误后清理资源
  • 验证返回值

工具发现和更新

MCP支持动态工具发现:

  1. 客户端可以随时列出可用工具
  2. 服务器可以使用notifications/tools/list_changed通知客户端工具变更
  3. 工具可以在运行时添加或删除
  4. 工具定义可以更新(但应谨慎进行)

错误处理

工具错误应在结果对象中报告,而不是作为MCP协议级错误。这允许LLM看到并可能处理错误。当工具遇到错误时:

  1. 在结果中将isError设置为true
  2. content数组中包含错误详情

这是工具正确错误处理的示例:

// 处理工具执行
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("工具未找到");
});