MCP Resources 与 Prompts 机制

Resources 和 Prompts 是 MCP 协议中相对 Tools 的两大互补能力。本文详解 URI 资源寻址、资源订阅通知、提示词模板的参数化注入,以及三种能力在构建 AI 应用时的协同使用策略。

1. 三种能力的定位与区别

MCP 协议定义了三种核心能力:Tools、Resources 和 Prompts。它们各自承担不同的职责,共同构成了 AI 与外部世界交互的完整图景。

Tools(工具)——执行操作,产生副作用。AI 通过 Tools 对外部世界采取行动,如读写文件、调用 API、执行命令。工具调用是主动的、命令式的。

Resources(资源)——提供数据,只读访问。Resources 是外部系统向 AI 暴露的数据视图,如文件内容、数据库记录、API 响应。资源访问是被动的、查询式的。

Prompts(提示词模板)——预设对话模式。Prompts 定义了特定场景的对话模板和初始提示词,帮助 AI 快速进入角色或任务模式。提示词是结构化的、可复用的。

这三种能力的关系可以类比为:

  • Tools 是 AI 的"双手"——可以操作环境
  • Resources 是 AI 的"眼睛"——可以观察环境
  • Prompts 是 AI 的"角色卡"——定义了对话的风格和方向

2. Resources 深度解析

2.1 URI 寻址体系

Resources 使用 URI(统一资源标识符)作为资源的唯一标识。URI 不仅是标识,还承载了寻址语义:

// 文件资源
"file:///home/user/project/README.md"
"file:///var/log/nginx/access.log"

// 数据库资源
"db://postgres/main/users/schema"
"db://postgres/main/users/row/12345"

// API 资源
"api://github.com/repos/owner/repo/issues"
"api://stripe.com/customers/cus_12345"

// 自定义资源
"docs://internal/handbook/onboarding"
"config://app/production/database"

URI 的设计建议遵循以下原则:

  • 层级结构:从左到右表示从通用到具体的范围缩小
  • 可读性:URI 路径应该对人类可理解
  • 稳定性:资源的 URI 不应该频繁变化
  • 可预测性:给定资源类型,URI 的结构应该可以推断

2.2 资源注册与发现

服务器通过 resources/list 声明可用资源,但通常不提供完整列表(可能太多),而是支持 resources/list 带模板:

// 服务器声明的资源列表
const resourcesListResponse = {
  resources: [
    {
      uri: "file:///workspace/src/main.rs",
      name: "主入口文件",
      mimeType: "text/x-rust",
      size: 2048,
    },
    {
      uri: "db://app/users/count",
      name: "用户总数",
      mimeType: "application/json",
    },
    // 模板资源:实际的 URI 需要参数填充
    {
      uriTemplate: "file:///workspace/src/{module}.rs",
      name: "Rust 模块文件",
      mimeType: "text/x-rust",
    },
  ],
};

2.3 资源读取

客户端通过 resources/read 请求获取资源内容:

// 客户端请求
const readRequest = {
  jsonrpc: "2.0",
  id: 3,
  method: "resources/read",
  params: {
    uri: "file:///workspace/src/main.rs",
  },
};

// 服务器响应
const readResponse = {
  jsonrpc: "2.0",
  id: 3,
  result: {
    contents: [
      {
        uri: "file:///workspace/src/main.rs",
        mimeType: "text/x-rust",
        text: "fn main() {\n    println!(\"Hello, MCP!\");\n}",
      },
    ],
  },
};

2.4 资源订阅与通知

对于会变化的资源,客户端可以订阅更新通知:

// 客户端订阅
const subscribeRequest = {
  jsonrpc: "2.0",
  id: 4,
  method: "resources/subscribe",
  params: {
    uri: "file:///workspace/src/main.rs",
  },
};

// 服务器在资源变化时推送通知
const updateNotification = {
  jsonrpc: "2.0",
  method: "notifications/resources/updated",
  params: {
    uri: "file:///workspace/src/main.rs",
  },
};

订阅机制在以下场景特别有价值:

  • 文件监控:代码修改后 AI 助手自动感知变化
  • 数据仪表板:实时数据变化推送给 AI 进行告警分析
  • 协作编辑:多人编辑场景下 AI 获取最新文档状态

2.5 资源实现示例

// src/resources.ts
interface ResourceDefinition {
  uri: string;
  name: string;
  mimeType: string;
  read: () => Promise<string>;
  watchable: boolean;
}

class ResourceManager {
  private resources: Map<string, ResourceDefinition> = new Map();
  private subscribers: Map<string, Set<string>> = new Map();
  private watchers: Map<string, ReturnType<typeof setInterval>> = new Map();

  register(def: ResourceDefinition): void {
    this.resources.set(def.uri, def);
  }

  async read(uri: string): Promise<{ uri: string; text: string; mimeType: string }> {
    const resource = this.resources.get(uri);
    if (!resource) throw new Error(`资源不存在: ${uri}`);

    const content = await resource.read();
    return {
      uri,
      text: content,
      mimeType: resource.mimeType,
    };
  }

  subscribe(uri: string, clientId: string): void {
    const resource = this.resources.get(uri);
    if (!resource || !resource.watchable) {
      throw new Error(`资源不可订阅: ${uri}`);
    }

    if (!this.subscribers.has(uri)) {
      this.subscribers.set(uri, new Set());
    }
    this.subscribers.get(uri)!.add(clientId);

    // 启动文件系统监控(如果尚未启动)
    this.startWatching(uri);
  }

  private startWatching(uri: string): void {
    if (this.watchers.has(uri)) return;

    // 使用定时轮询模拟文件监控
    const watcher = setInterval(async () => {
      const subs = this.subscribers.get(uri);
      if (!subs || subs.size === 0) {
        clearInterval(watcher);
        this.watchers.delete(uri);
        return;
      }

      // 检测变化并通知
      // 实际实现中应使用 fs.watch 或 inotify
      this.notifyUpdate(uri);
    }, 5000);

    this.watchers.set(uri, watcher);
  }

  private notifyUpdate(uri: string): void {
    const notification = {
      jsonrpc: "2.0",
      method: "notifications/resources/updated",
      params: { uri },
    };

    // 通过所有连接的客户端发送通知
    // ...
  }
}

3. Prompts 深度解析

3.1 提示词模板的用途

Prompts 解决了 AI 应用中的几个痛点:

  • 角色模式切换:一键切换 AI 到特定角色(代码审查者、法律顾问、写作助手)
  • 对话初始化:为特定任务准备包含上下文的初始提示词
  • 提示词复用:将反复使用的提示词模式抽象为可参数化的模板
  • 知识注入:将特定领域的知识和指令打包为可复用单元

3.2 提示词定义

// 服务器声明的 Prompts
const promptsListResponse = {
  prompts: [
    {
      name: "code_review",
      description: "审查代码变更并提供改进建议",
      arguments: [
        {
          name: "file_path",
          description: "要审查的文件路径",
          required: true,
        },
        {
          name: "focus",
          description: "审查重点",
          required: false,
        },
      ],
    },
    {
      name: "sql_expert",
      description: "以数据库专家身份协助查询优化",
      arguments: [],
    },
    {
      name: "bug_bash",
      description: "系统性分析代码中潜在的 Bug",
      arguments: [
        {
          name: "scope",
          description: "分析范围:file 或 directory",
          required: true,
        },
        {
          name: "target",
          description: "目标文件或目录路径",
          required: true,
        },
      ],
    },
  ],
};

3.3 提示词内容获取

// 客户端请求获取完整提示词
const getPromptRequest = {
  jsonrpc: "2.0",
  id: 5,
  method: "prompts/get",
  params: {
    name: "code_review",
    arguments: {
      file_path: "/src/auth.ts",
      focus: "security",
    },
  },
};

// 服务器返回结构化提示词
const getPromptResponse = {
  jsonrpc: "2.0",
  id: 5,
  result: {
    description: "审查 /src/auth.ts 的安全性",
    messages: [
      {
        role: "system",
        content: {
          type: "text",
          text: `
            你是一位专注于安全的代码审查专家。
            你的任务是审查代码中的安全漏洞,包括但不限于:
            - SQL 注入
            - XSS 漏洞
            - 认证和授权缺陷
            - 敏感信息泄露
            - 不安全的反序列化

            对每处发现的问题,请提供:
            1. 问题描述
            2. 风险等级(高/中/低)
            3. 修复建议(含代码示例)
          `,
        },
      },
      {
        role: "user",
        content: {
          type: "text",
          text: "请审查文件 /src/auth.ts 的安全性问题。特别关注:authentication flows, password handling, session management。",
        },
      },
    ],
  },
};

3.4 动态提示词生成

class PromptManager {
  private templates: Map<string, PromptTemplate> = new Map();

  register(template: PromptTemplate): void {
    this.templates.set(template.name, template);
  }

  async get(
    name: string,
    args: Record<string, string>
  ): Promise<PromptResult> {
    const template = this.templates.get(name);
    if (!template) throw new Error(`未知提示词: ${name}`);

    // 验证必需参数
    for (const arg of template.arguments) {
      if (arg.required && !args[arg.name]) {
        throw new Error(`缺少必需参数: ${arg.name}`);
      }
    }

    // 生成动态内容
    const messages = await template.generate(args);

    return {
      description: template.renderDescription(args),
      messages,
    };
  }
}

interface PromptTemplate {
  name: string;
  description: string;
  arguments: Array<{ name: string; description: string; required: boolean }>;
  generate: (args: Record<string, string>) => Promise<Message[]>;
  renderDescription: (args: Record<string, string>) => string;
}

// 注册 SQL 专家提示词
const sqlExpertTemplate: PromptTemplate = {
  name: "sql_expert",
  description: "数据库查询优化助手",
  arguments: [
    { name: "database_type", description: "数据库类型", required: false },
    { name: "table_schema", description: "表结构信息", required: false },
  ],

  async generate(args) {
    const dbType = args.database_type ?? "unspecified";
    const schema = args.table_schema ?? "";

    return [
      {
        role: "system",
        content: {
          type: "text",
          text: `
            你是一名 ${dbType} 数据库专家,专注于查询优化。

            在回答查询相关问题时,你会:
            1. 分析查询的执行计划
            2. 建议索引优化方案
            3. 重写低效查询
            4. 解释数据库特定的优化特性

            ${schema ? "目标表结构信息:\n" + schema : ""}
          `,
        },
      },
    ];
  },

  renderDescription(args) {
    return args.database_type
      ? `${args.database_type} 查询优化专家`
      : "数据库查询优化助手";
  },
};

4. 三种能力的协同使用

4.1 代码分析工作流

用户: "分析这个项目中所有 API 端点的安全性"

AI 思考过程:
1. [Resources] 读取项目结构和文件列表
2. [Tools] search_files 查找路由定义和控制器
3. [Resources] 读取关键文件内容
4. [Prompts] 使用 security_review 角色
5. [Tools] 对每个端点调用 analyze_endpoint
6. [结果汇总并向用户报告]

4.2 数据分析工作流

用户: "分析本月销售数据并生成报告"

AI 思考过程:
1. [Prompts] 启用 data_analyst 角色
2. [Tools] db_execute_select 获取销售数据
3. [Tools] statistical_analysis 计算指标
4. [Tools] generate_chart 生成可视化
5. [Resources] 读取公司报告模板
6. [结果整合为 Markdown 报告]

4.3 最佳组合策略

场景主要能力辅助能力说明
增量代码修改ToolsResources先读取当前代码,再修改
大规模重构PromptsTools + Resources使用重构角色,配合工具执行
数据分析ResourcesTools资源提供数据,工具做计算
文档生成PromptsResources角色定义风格,资源提供素材
Bug 修复ToolsResources工具修改代码,资源提供上下文

5. 高级资源模式

5.1 虚拟资源

虚拟资源不对应磁盘上的实际文件,而是动态生成的内容:

// Git 提交历史作为资源
const gitLogResource: ResourceDefinition = {
  uri: "git://project/log/main/20",
  name: "最近 20 条 main 分支提交",
  mimeType: "text/markdown",

  async read() {
    const log = await execGit(["log", "-20", "--oneline", "--decorate", "main"]);
    return "## 最近提交\n\n" + log.split("\n").map((l) => `- ${l}`).join("\n");
  },

  watchable: false,
};

5.2 组合资源视图

将多个数据源组合为统一的资源视图:

// 项目健康度仪表板
const healthDashboardResource: ResourceDefinition = {
  uri: "dashboard://project/health",
  name: "项目健康度仪表板",
  mimeType: "application/json",

  async read() {
    const [testResults, coverage, lintIssues, deps] = await Promise.all([
      runTests(),
      getCoverage(),
      runLinter(),
      checkOutdatedDeps(),
    ]);

    return JSON.stringify(
      {
        test_suite: testResults,
        code_coverage: coverage,
        lint_violations: lintIssues,
        outdated_dependencies: deps,
        overall_score: calculateScore({ testResults, coverage, lintIssues, deps }),
      },
      null,
      2
    );
  },

  watchable: true,
};

6. 总结

Resources 和 Prompts 是 MCP 工具生态的重要补充:

  • Resources 让 AI 能够被动地获取环境信息,适合提供上下文和观察系统状态
  • Prompts 让服务器可以贡献领域知识和对话模板,提升特定场景下的表现
  • Tools 让 AI 能够主动地修改环境,是 Resources 和 Prompts 的"执行层"

一个完整的 MCP 服务器应该根据场景选择合适的能力暴露方式:有时纯 Resources 就能满足需求(如只读数据查询),有时纯 Tools 更合适(如执行命令),而复杂的交互场景需要三种能力的协同配合。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 排序学习与搜索召回排序系统
  2. 数据版本控制与血缘:DVC 与 LakeFS
  3. 模型可解释性:SHAP、LIME 与注意力归因