第5章:Tools与Resources详解

在上一章中,我们学习了如何创建一个基础的 MCP Server。本章将深入探讨 MCP 协议中两个核心概念:Tools(工具)Resources(资源)。理解这两者的区别和使用场景,是开发功能完善的 MCP Server 的关键。

一、Tools(工具)概念

Tools 是 MCP 协议中最重要的组成部分之一。简单来说,Tools 就是 AI 可以调用的函数或方法,用于执行特定的操作并返回结果。

核心理解:Tools 是 AI 的"手"——让 AI 能够执行实际操作,如计算、查询、写入文件等。

Tools 的特点

  • 主动性:AI 可以主动决定何时调用工具
  • 可执行性:工具可以执行操作(读取、写入、计算等)
  • 返回结果:工具执行后会返回结果给 AI
  • 参数化:工具可以接受参数,实现灵活调用

举个例子:当用户问"3乘以5等于多少?"时,AI 发现有一个"calculator"工具可用,于是决定调用这个工具来计算,而不是自己直接回答。这样可以确保计算的准确性。

二、Tool 定义结构

每个 Tool 都需要定义以下核心属性:

属性 类型 说明
name string 工具的唯一标识名称,只能包含字母、数字和下划线
description string 工具的功能描述,这是 AI 决定是否调用的关键
inputSchema object 使用 JSON Schema 定义工具的参数结构

完整的 Tool 定义示例

{
  "name": "calculator",
  "description": "执行数学计算,支持加减乘除、幂运算等。当用户需要计算数学表达式时使用此工具。",
  "inputSchema": {
    "type": "object",
    "properties": {
      "expression": {
        "type": "string",
        "description": "要计算的数学表达式,例如 '3 + 5 * 2'"
      }
    },
    "required": ["expression"]
  }
}
描述的重要性:description 字段决定了 AI 是否会在合适的时候调用这个工具。描述要清晰、具体,说明工具的用途和使用场景。

JSON Schema 详解

inputSchema 使用标准的 JSON Schema 格式定义参数。以下是常用的类型和约束:

{
  "type": "object",
  "properties": {
    // 字符串类型
    "username": {
      "type": "string",
      "description": "用户名",
      "minLength": 3,
      "maxLength": 20
    },
    // 数字类型
    "age": {
      "type": "number",
      "description": "年龄",
      "minimum": 0,
      "maximum": 150
    },
    // 整数类型
    "count": {
      "type": "integer",
      "description": "数量"
    },
    // 布尔类型
    "active": {
      "type": "boolean",
      "description": "是否激活"
    },
    // 数组类型
    "tags": {
      "type": "array",
      "description": "标签列表",
      "items": {
        "type": "string"
      }
    },
    // 枚举类型
    "priority": {
      "type": "string",
      "description": "优先级",
      "enum": ["low", "medium", "high"]
    }
  },
  "required": ["username", "age"]
}
最佳实践:始终为每个参数提供 description,这有助于 AI 理解如何正确使用工具。

三、多个 Tools 示例

一个 MCP Server 可以包含多个 Tools。下面我们定义三个实用的工具:

1. 计算器工具

{
  "name": "calculator",
  "description": "执行数学计算,支持加减乘除、幂运算、取模等。当用户需要计算数值时使用。",
  "inputSchema": {
    "type": "object",
    "properties": {
      "expression": {
        "type": "string",
        "description": "数学表达式,例如 '15 * 4 + 10' 或 'Math.sqrt(16)'"
      }
    },
    "required": ["expression"]
  }
}

2. 天气查询工具

{
  "name": "get_weather",
  "description": "获取指定城市的当前天气信息。当用户询问某个城市的天气时使用此工具。",
  "inputSchema": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "城市名称,例如 '北京'、'上海'、'New York'"
      },
      "unit": {
        "type": "string",
        "description": "温度单位",
        "enum": ["celsius", "fahrenheit"],
        "default": "celsius"
      }
    },
    "required": ["city"]
  }
}

3. 文件读取工具

{
  "name": "read_file",
  "description": "读取指定路径的文件内容。当用户需要查看文件内容时使用此工具。仅支持读取文本文件。",
  "inputSchema": {
    "type": "object",
    "properties": {
      "filePath": {
        "type": "string",
        "description": "文件的完整路径,例如 '/home/user/document.txt'"
      },
      "encoding": {
        "type": "string",
        "description": "文件编码格式",
        "enum": ["utf-8", "gbk", "latin1"],
        "default": "utf-8"
      }
    },
    "required": ["filePath"]
  }
}
工具命名规范:使用小写字母和下划线(snake_case),避免使用空格和特殊字符。

四、Resources(资源)概念

Resources 是 MCP 协议中的另一个重要概念。与 Tools 不同,Resources 提供的是只读数据访问能力,如文件内容、数据库记录、API 返回的数据等。

核心理解:Resources 是 AI 的"眼睛"——让 AI 能够读取和访问各种数据源,但不能修改数据。

Resources 的特点

  • 只读性:Resources 是只读的,不会修改数据
  • 数据访问:用于访问文件、数据库、API 等数据源
  • URI 标识:通过 URI(统一资源标识符)来标识资源
  • MIME 类型:明确指定数据的类型(text/plain、application/json 等)

五、Resource 定义

Resource 的定义比 Tool 简单,主要包括:

属性 类型 说明
uri string 资源的唯一标识符,建议使用标准 URI 格式
name string 资源的可读名称
mimeType string 资源的 MIME 类型(可选)
description string 资源的描述(可选)

Resource 定义示例

示例 1:日志文件资源

{
  "uri": "file:///var/log/app.log",
  "name": "应用日志文件",
  "mimeType": "text/plain",
  "description": "应用程序的运行日志,包含错误和警告信息"
}

示例 2:配置文件资源

{
  "uri": "file:///etc/myapp/config.json",
  "name": "应用配置文件",
  "mimeType": "application/json",
  "description": "应用程序的配置参数"
}

示例 3:数据库查询结果资源

{
  "uri": "db://users/active",
  "name": "活跃用户列表",
  "mimeType": "application/json",
  "description": "数据库中所有活跃用户的信息"
}
URI 格式建议:虽然可以使用任意格式的 URI,但建议使用标准格式如 file://db://api:// 等,这样更易于理解和维护。

六、Tools vs Resources 对比

理解 Tools 和 Resources 的区别非常重要,下面通过对比表格来清晰展示两者的差异:

特性 Tools(工具) Resources(资源)
主要用途 执行操作(计算、写入、发送请求等) 提供数据访问(读取文件、查询数据等)
数据修改 ✅ 可以修改数据(写入文件、更新数据库等) ❌ 只读,不修改数据
调用方式 AI 主动决定调用,需传入参数 AI 通过 URI 读取,类似访问 URL
定义内容 name、description、inputSchema uri、name、mimeType
返回值 执行结果(计算结果、操作状态等) 资源内容(文本、JSON、二进制等)
典型示例 计算器、发送邮件、创建文件 日志文件、配置文件、数据库记录
选择建议:
• 需要 AI 执行操作并获取结果 → 使用 Tool
• 需要 AI 读取数据但不修改 → 使用 Resource

七、Prompts(提示模板)

除了 Tools 和 Resources,MCP 还支持 Prompts(提示模板)。Prompts 允许 Server 提供预定义的提示词模板,帮助用户更高效地使用 AI。

Prompt 定义示例

{
  "name": "code_review",
  "description": "代码审查助手 - 帮助检查代码中的潜在问题",
  "arguments": [
    {
      "name": "language",
      "description": "编程语言",
      "required": true
    },
    {
      "name": "code",
      "description": "需要审查的代码",
      "required": true
    }
  ]
}

当用户选择这个 Prompt 时,AI 会自动填充模板内容:

请作为 ${language} 专家,审查以下代码:

\`\`\`${language}
${code}
\`\`\`

请检查:
1. 潜在的 bug 和安全问题
2. 代码风格和最佳实践
3. 性能优化建议
4. 可读性改进建议
使用场景:Prompts 特别适合需要重复使用相同提示词格式的场景,如代码审查、文档生成、翻译等。

八、完整实战:开发多功能 Server

现在,让我们综合运用本章所学知识,开发一个功能完整的 MCP Server。这个 Server 将包含三个实用的 Tools:计算器、时间查询、随机数生成。

项目结构

multi-tool-server/
├── package.json
└── server.js

1. package.json

{
  "name": "multi-tool-server",
  "version": "1.0.0",
  "description": "多功能 MCP Server - 包含计算器、时间查询、随机数生成",
  "type": "module",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}

2. server.js(完整代码)

#!/usr/bin/env node

/**
 * 多功能 MCP Server
 * 提供三个工具:计算器、时间查询、随机数生成
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

// ===== 1. 创建 Server 实例 =====
const server = new Server(
  {
    name: 'multi-tool-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},  // 声明支持 tools 能力
    },
  }
);

// ===== 2. 定义 Tools =====

/**
 * 工具列表定义
 * 包含三个工具:计算器、时间查询、随机数生成
 */
const TOOLS = [
  {
    name: 'calculator',
    description: `执行数学计算,支持:
- 基础运算:+、-、*、/、%(取模)
- 幂运算:Math.pow(a, b)
- 平方根:Math.sqrt(x)
- 三角函数:Math.sin(x)、Math.cos(x)、Math.tan(x)
- 其他:Math.abs(x)、Math.round(x)、Math.floor(x) 等

当用户需要计算数学表达式时使用此工具。`,
    inputSchema: {
      type: 'object',
      properties: {
        expression: {
          type: 'string',
          description: '数学表达式,例如 "15 * 4 + 10" 或 "Math.sqrt(16) + 5"',
        },
      },
      required: ['expression'],
    },
  },
  {
    name: 'get_current_time',
    description: '获取当前日期和时间信息。当用户询问"现在几点"、"今天几号"、"当前时间"等问题时使用此工具。',
    inputSchema: {
      type: 'object',
      properties: {
        timezone: {
          type: 'string',
          description: '时区,例如 "Asia/Shanghai"、"America/New_York"、"UTC"。如果不指定,使用本地时区。',
        },
        format: {
          type: 'string',
          description: '时间格式',
          enum: ['full', 'date', 'time', 'iso'],
          default: 'full',
        },
      },
    },
  },
  {
    name: 'generate_random',
    description: '生成随机数。当用户需要随机数、掷骰子、随机选择等场景时使用此工具。',
    inputSchema: {
      type: 'object',
      properties: {
        min: {
          type: 'number',
          description: '最小值(包含),默认为 0',
          default: 0,
        },
        max: {
          type: 'number',
          description: '最大值(包含),默认为 100',
          default: 100,
        },
        count: {
          type: 'integer',
          description: '生成数量,默认为 1',
          default: 1,
        },
        decimals: {
          type: 'integer',
          description: '小数位数,0表示整数,默认为 0',
          default: 0,
        },
      },
    },
  },
];

// ===== 3. 实现工具处理器 =====

/**
 * 处理 calculator 工具
 * @param {string} expression - 数学表达式
 * @returns {object} 计算结果
 */
function handleCalculator(expression) {
  try {
    // 安全评估数学表达式
    // 注意:实际生产环境中应该使用更安全的数学表达式解析器
    const result = Function('"use strict"; return (' + expression + ')')();
    
    return {
      content: [
        {
          type: 'text',
          text: `计算结果:${expression} = ${result}`,
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `计算错误:${error.message}。请检查表达式是否正确。`,
        },
      ],
      isError: true,
    };
  }
}

/**
 * 处理 get_current_time 工具
 * @param {string} timezone - 时区
 * @param {string} format - 格式
 * @returns {object} 时间信息
 */
function handleGetCurrentTime(timezone, format) {
  const now = new Date();
  
  let timeString;
  switch (format) {
    case 'date':
      timeString = now.toLocaleDateString('zh-CN');
      break;
    case 'time':
      timeString = now.toLocaleTimeString('zh-CN');
      break;
    case 'iso':
      timeString = now.toISOString();
      break;
    case 'full':
    default:
      timeString = now.toLocaleString('zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        weekday: 'long',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        timeZoneName: 'short',
      });
      break;
  }

  return {
    content: [
      {
        type: 'text',
        text: `当前时间:${timeString}${timezone ? '(时区:' + timezone + ')' : ''}`,
      },
    ],
  };
}

/**
 * 处理 generate_random 工具
 * @param {number} min - 最小值
 * @param {number} max - 最大值
 * @param {number} count - 数量
 * @param {number} decimals - 小数位数
 * @returns {object} 随机数结果
 */
function handleGenerateRandom(min, max, count, decimals) {
  const results = [];
  
  for (let i = 0; i < count; i++) {
    let num = Math.random() * (max - min) + min;
    if (decimals === 0) {
      num = Math.floor(num);
    } else {
      num = Number(num.toFixed(decimals));
    }
    results.push(num);
  }

  let text;
  if (count === 1) {
    text = `生成的随机数:${results[0]}`;
  } else {
    text = `生成的 ${count} 个随机数(${decimals === 0 ? '整数' : decimals + '位小数'},范围 ${min}-${max}):\n${results.join(', ')}`;
  }

  return {
    content: [
      {
        type: 'text',
        text: text,
      },
    ],
  };
}

// ===== 4. 注册请求处理器 =====

/**
 * 处理 ListTools 请求
 * 返回所有可用的工具列表
 */
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools: TOOLS };
});

/**
 * 处理 CallTool 请求
 * 根据工具名称分发到对应的处理器
 */
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'calculator':
      return handleCalculator(args.expression);
    
    case 'get_current_time':
      return handleGetCurrentTime(args.timezone, args.format || 'full');
    
    case 'generate_random':
      return handleGenerateRandom(
        args.min ?? 0,
        args.max ?? 100,
        args.count ?? 1,
        args.decimals ?? 0
      );
    
    default:
      throw new Error(`未知工具:${name}`);
  }
});

// ===== 5. 启动 Server =====

async function main() {
  const transport = new StdioServerTransport();
  
  console.error('多功能 MCP Server 启动中...');
  console.error('可用工具:calculator、get_current_time、generate_random');
  
  await server.connect(transport);
  console.error('Server 已连接,等待请求...');
}

main().catch((error) => {
  console.error('Server 错误:', error);
  process.exit(1);
});

3. 测试 Server

安装依赖并启动 Server:

npm install
npm start

在支持 MCP 的客户端(如 Claude Desktop)中配置 Server 后,你可以测试以下对话:

测试示例:
  • "帮我计算 123 乘以 456 等于多少" → 调用 calculator
  • "现在几点了" → 调用 get_current_time
  • "生成一个 1 到 100 的随机数" → 调用 generate_random
  • "掷一个六面骰子" → 调用 generate_random (min=1, max=6)

代码解析

  1. 工具定义(TOOLS 数组):清晰描述每个工具的名称、用途和参数
  2. 处理器函数:为每个工具实现具体的业务逻辑
  3. 请求分发:通过 switch-case 根据工具名称调用对应的处理器
  4. 错误处理:在 calculator 等工具中捕获并返回错误信息
  5. 默认参数:使用 ?? 运算符为可选参数提供默认值

本章小结

本章详细介绍了 MCP 协议中的核心概念:

  • Tools:AI 可调用的函数,用于执行操作并返回结果。需要定义 name、description 和 inputSchema。
  • Resources:只读数据访问,通过 URI 标识,适合提供文件、配置等数据。
  • Prompts:预定义的提示词模板,帮助用户高效使用 AI。
关键区别:Tools 用于"做事情"(可修改数据),Resources 用于"看数据"(只读访问)。

通过实战项目,我们开发了一个包含三个实用工具的多功能 Server。你可以基于这个模板,继续添加更多工具,如文件操作、网络请求、数据库查询等。

下一章,我们将学习如何开发 MCP Client,在自己的应用程序中集成和使用 MCP Server。