注册了 MCP Resource,模型为什么仍然看不到?

MCP Server 不只能提供 Tool,还可以提供 Resource。

Tool 很容易理解:模型需要查询用户、读取文件或执行命令时,发起一次工具调用,Runtime 执行后把结果交还给模型。

Resource 看起来更像"资料":使用指南、配置说明、数据库结构、项目文档,都可以通过 MCP Server 暴露出来。

但这里有一个很容易产生的误解:

text 复制代码
Server 注册了 Resource,模型就能自动看到它。

实际上,注册只代表这份资源可以被 MCP Client 发现和读取。它什么时候被读取、哪些内容进入 Prompt,仍然由 Host 应用决定。

本文结合一个最小 Demo,把下面这条链路完整拆开:

text 复制代码
registerResource
→ listResources
→ readResource
→ SystemMessage
→ LLM

Demo 中的 Tool 和 Resource

这个 MCP Server 同时注册了两种能力:

text 复制代码
Tool:query_user
Resource:docs://guide

query_user 接收用户 ID,再从模拟数据库中查询用户信息。

docs://guide 则提供 MCP Server 的使用指南。

它们虽然来自同一个 Server,但进入模型上下文的方式不同:

能力 谁决定使用 进入上下文的方式
Tool 通常由模型根据任务决定 Runtime 执行后包装成 ToolMessage
Resource Host 应用决定读取哪些内容 应用读取后主动放入 messages

可以先记住一句话:

text 复制代码
Tool 等模型发起调用,Resource 等应用主动读取。

第一步:在 MCP Server 中注册 Resource

Server 使用 registerResource() 注册使用指南:

js 复制代码
server.registerResource(
  '使用指南',
  'docs://guide',
  {
    description: '使用指南',
    mimeType: 'text/plain',
  },
  async () => {
    return {
      contents: [
        {
          uri: 'docs://guide',
          mimeType: 'text/plain',
          text: `
            MCP Server 使用指南
            功能:提供用户查询等工具。
            使用:在 MCP Client 中通过自然语言对话,
            Client 会调用相应工具。
          `,
        },
      ],
    };
  }
);

这里有四个关键部分:

  • 使用指南:Resource 的名称
  • docs://guide:Resource 的 URI
  • mimeType:内容类型
  • 回调函数:真正返回资源内容

docs://guide 不一定是一个能够在浏览器中打开的网址。

它更像 MCP 内部使用的资源标识:Client 先通过 URI 发现资源,再使用这个 URI 请求实际内容。

注册成功不等于模型已经看到

当 Server 执行完 registerResource(),它只完成了:

text 复制代码
"我这里有一份 docs://guide,可以通过 MCP 读取。"

它还没有完成:

text 复制代码
"把 docs://guide 的正文发送给大模型。"

模型调用接口时,真正能看到的是当前请求里的 messages、绑定的 Tools 和其他显式上下文。

如果 Host 没有读取 Resource,也没有把内容加入 messages,模型不会因为 MCP Server 中存在这份资料就自动知道它。

为什么不默认把所有 Resource 全部塞给模型?

  • Resource 可能很多,全部加入会消耗大量 Token
  • 有些资料与当前问题无关,会干扰模型判断
  • 不同用户可能拥有不同的资料访问权限
  • 某些内容只应在特定任务中加载

因此,Resource 的选择和注入必须留在应用层控制。

第二步:Client 发现可用 Resource

Host 中先创建 MultiServerMCPClient,连接本地 MCP Server:

js 复制代码
const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node',
      args: ['/项目路径/my-mcp-server.mjs'],
    },
  },
});

本地 Server 会作为子进程启动,Client 通过 stdio 与它通信。

接下来调用:

js 复制代码
const resources = await mcpClient.listResources();

listResources() 获取的是资源列表,作用是回答:

text 复制代码
当前连接的 MCP Server 提供了哪些 Resource?

返回结果按 Server 组织,因此代码需要遍历 serverName 和对应的资源列表:

js 复制代码
for (const [serverName, resources] of Object.entries(res)) {
  for (const resource of resources) {
    // 下一步读取资源
  }
}

这一步仍然只是发现资源,还没有获得 docs://guide 的正文。

第三步:通过 URI 读取 Resource 内容

发现资源后,再调用 readResource()

js 复制代码
const content = await mcpClient.readResource(
  serverName,
  resource.uri
);

这里需要两个信息:

text 复制代码
serverName:资源来自哪个 MCP Server
resource.uri:具体读取哪一个 Resource

为什么不能只传 URI?

因为一个 Client 可以同时连接多个 MCP Server。Host 既要知道资源标识,也要知道应该向哪个 Server 发起读取请求。

Server 返回的内容可能包含多个条目,因此 Demo 会继续遍历:

js 复制代码
let resourcesContent = '';

for (const [serverName, resources] of Object.entries(res)) {
  for (const resource of resources) {
    const content = await mcpClient.readResource(
      serverName,
      resource.uri
    );

    for (const item of content) {
      if (item.text) {
        resourcesContent += item.text;
      }
    }
  }
}

执行到这里,Host 才真正拿到了 Resource 的文本内容。

第四步:把 Resource 放入 SystemMessage

拿到 resourcesContent 以后,应用主动创建 SystemMessage

js 复制代码
const messages = [
  new SystemMessage(resourcesContent),
  new HumanMessage(query),
];

至此,Resource 才真正进入模型上下文。

完整过程是:

text 复制代码
MCP Server 注册 Resource
  ↓
Host 调用 listResources() 发现资源
  ↓
Host 调用 readResource() 读取正文
  ↓
Host 筛选、整理资源内容
  ↓
Host 创建 SystemMessage
  ↓
LLM 在 messages 中看到 Resource

这个 Demo 直接拼接了所有文本内容,适合资源数量很少的最小示例。

真实项目通常还要根据 URI、名称、用户权限和当前任务筛选资源,不能不加判断地把所有内容全部放入 Prompt。

Resource 为什么不需要 ToolMessage?

ToolMessage 用来回传模型已经发起的 Tool Call 结果,因此必须带上对应的 tool_call_id

Resource 的流程不同。

它在第一次调用模型之前,就已经由 Host 读取并放进 SystemMessage

text 复制代码
SystemMessage(Resource 内容)
HumanMessage(用户问题)
→ LLM

模型没有先生成 Resource Call,Runtime 也没有执行 Tool Call,因此这里不需要 ToolMessage

如果用户询问:

text 复制代码
MCP Server 使用指南是什么?

模型可以直接根据 SystemMessage 中的指南回答。

如果用户改问:

text 复制代码
查询 ID 为 001 的用户信息。

模型才需要发起 query_user Tool Call,Runtime 执行后再通过 ToolMessage 返回查询结果。

同一个 MCP Server 中,两条链路可以同时存在:

text 复制代码
Resource:Host 读取 → SystemMessage → LLM
Tool:LLM 发起 → Runtime 执行 → ToolMessage → LLM

Resource 和 RAG 有什么区别?

Resource 与 RAG 都可以为模型补充外部知识,但处理方式不同。

当前 Demo 的使用指南很短,可以一次性放进上下文:

text 复制代码
读取完整 Resource → 放入 SystemMessage

如果资料变成几百份文档、几十万段文本,就不适合全部塞入 Prompt。这时更适合先建立向量索引,再根据问题只检索相关片段:

text 复制代码
用户问题 → RAG 检索相关片段 → 放入 Prompt

可以这样区分:

场景 更适合的方式
简短、稳定、每次都需要的说明 直接读取 MCP Resource
大量文档,需要按问题查找相关片段 RAG 检索
需要执行查询、写文件或调用接口 MCP Tool

Resource 不是 RAG 的替代品,RAG 也不是 Resource 的替代品。它们解决的是不同规模和不同控制方式的上下文问题。

任务结束后仍然要关闭 Client

这个 Demo 通过 command + args 启动本地 MCP Server 子进程。

完成任务后需要关闭连接:

js 复制代码
await mcpClient.close();

否则子进程和 stdio 通信通道可能仍然存在,Node.js 脚本就会一直保持运行。

更稳妥的写法是放进 finally

js 复制代码
try {
  await runAgentWithTools(query);
} finally {
  await mcpClient.close();
}

无论模型回答成功还是中途报错,都能释放 MCP 连接和子进程。

总结

registerResource() 只负责在 MCP Server 中声明资源,并不会自动修改模型上下文。

Resource 真正进入模型,需要 Host 明确完成四步:

text 复制代码
listResources()
→ readResource(serverName, uri)
→ 整理资源文本
→ SystemMessage(resourcesContent)

这也解释了 MCP Resource 的核心控制关系:

text 复制代码
Server 负责提供资源
Client 负责发现和读取资源
Host 负责筛选并注入上下文
LLM 负责理解内容并生成答案

模型不会自动读取所有资料,也不应该自动获得所有资料。把什么内容放入上下文、什么时候放、允许谁读取,最终仍然是 AI 应用需要负责的工程问题。

相关推荐
正律有为1 小时前
跨境手机壳、印花服饰避坑:游戏手柄无缝涂鸦插画版权自查实操步骤
人工智能
无己心1 小时前
中国医美行业GEO白皮书(2026)
人工智能
_codeOH1 小时前
MCP 协议深度解读:AI 应用的"USB-C"时刻
人工智能·ai编程
河南花仙子科技1 小时前
小游戏开发中常见的BUG类型及成因
人工智能·科技·游戏·bug
禅与计算机程序设计艺术1 小时前
【推荐一款比腾讯WorkBuddy、阿里悟空更好用能力更强的办公自进化 Agent 】开源 DeepThink AI Agent
人工智能
hxhy1 小时前
第6篇:多 Agent 协作 —— 为什么需要多个智能体
人工智能
沫儿笙1 小时前
弧焊机器人氩气省气装置
人工智能·机器人
Luhui Dev1 小时前
用 AI 生成一道几何题配图:三角形四心作图案例
人工智能·数学·agent·luhuidev
爬楼的猪1 小时前
html-edge-tts-video: 用AI创建有声视频
人工智能·ai编程