漫谈 MCP 构建之Resources篇

今天是漫谈 MCP 系列的第二篇文章,在上一篇主要介绍了一些核心概念,这篇正式开始 MCP Server 的开发之旅。

关于 MCP server 的构建会遵循概念篇会有三篇文章分别为:

  1. Resources
  2. Tools
  3. Prompts

今天先从 Resources 开始。首先回顾一下 Resources 的核心作用:它通过暴露一系列结构化的、只读的数据源,为模型提供完成任务所需的上下文,从而有效避免模型产生幻觉。根据 MCP 的设计,资源是**"应用控制的"(Application-Controlled)**,这意味着由客户端应用(而非 LLM)来决定何时以及如何读取这些资源。

理解资源

静态与动态

资源主要通过以下两种方式提供:

  1. 静态资源,URI 是一个固定地址,例如 calendar://events/2024,它代表日历 2024 年的可用日期。
  2. 资源模板,URI 包含占位符,例如travel://activities/{city}/{category} ,需要传递城市和类别才返回相关信息。

需要注意的是,每个资源都由一个唯一的 URI(统一资源标识符)来定位。同时,每个资源都应有一个 MIME 类型(如 application/json),用于告知客户端其内容的数据格式。

核心协议

下面是一些接口,应用程序可以通过这些接口来发现资源、检索等,这里了解即可。

Method 描述 返回值
resources/list 列出可用的直接资源 资源描述符(数组)
resources/templates/list 资源模板列表 资源模板定义(数组)
resources/read 检索资源 带有元数据的资源数据
resources/subscribe 监听资源变化 确定订阅

开发准备

技术选型 Python vs. TypeScript SDK

笔者本人主要是使用 TypeScript 的,但是考虑生态以及后续的文章,这里还是通过 Python 来进行讲解,后续的代码会附加很多注释,所以不用担心看不懂的问题。此外值得一提的是,我觉得 Python 本身的装饰器确实很好用,尤其是可以支持到函数本身,所以使用 Python 的 SDK 确实会提高很多效率。

这里可以看一组对比:

1. Python SDK

PY 复制代码
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(name="Resource Example")


@mcp.resource("file://documents/{name}")
def read_document(name: str) -> str:
    """Read a document by name."""
    # This would normally read from disk
    return f"Content of {name}"


@mcp.resource("config://settings")
def get_settings() -> str:
    """Get application settings."""
    return """{
  "theme": "dark",
  "language": "en",
  "debug": false
}"""

2. TypeScript SDK

TS 复制代码
// Static resource
server.registerResource(
  "config",
  "config://app",
  {
    title: "Application Config",
    description: "Application configuration data",
    mimeType: "text/plain"
  },
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// Dynamic resource with parameters
server.registerResource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  {
    title: "User Profile",
    description: "User profile information"
  },
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);

// Resource with context-aware completion
server.registerResource(
  "repository",
  new ResourceTemplate("github://repos/{owner}/{repo}", {
    list: undefined,
    complete: {
      // Provide intelligent completions based on previously resolved parameters
      repo: (value, context) => {
        if (context?.arguments?.["owner"] === "org1") {
          return ["project1", "project2", "project3"].filter(r => r.startsWith(value));
        }
        return ["default-repo"].filter(r => r.startsWith(value));
      }
    }
  }),
  {
    title: "GitHub Repository",
    description: "Repository information"
  },
  async (uri, { owner, repo }) => ({
    contents: [{
      uri: uri.href,
      text: `Repository: ${owner}/${repo}`
    }]
  })
);

环境配置

除此之外,我们还需要申请一个 API key,这里推荐阿里百炼,qwen 系列提供百万级的免费 token 额度,我认为作为测试来说完全够用了,此外关于 MCP host 我这边推荐使用 DeepChat

完成注册和下载之后,我们配置一下相关信息就可以正式开始今天的内容了。

实战

编写 Server 代码

bash 复制代码
uv init
uv add "mcp[cli]"

main.py

py 复制代码
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo")

# 模拟的配置数据
CONFIGS = {
    "alice": {"theme": "dark", "lang": "en", "editor": "vscode"},
    "bob": {"theme": "light", "lang": "zh", "editor": "cursor"},
    "default": {"theme": "dark", "lang": "en", "editor": "vim"},
}


@mcp.resource("configs://all")
def get_all_configs() -> dict:
    """
    获取并返回所有已知用户的完整配置信息列表。
    """
    return CONFIGS


@mcp.resource("configs://all-names")
def get_all_names() -> list[str]:
    """获取并返回一个仅包含所有用户名称的列表。"""
    return [item for item in CONFIGS]


@mcp.resource("configs://{name}")
def get_config_by_name(name: str) -> dict:
    """
    根据提供的单个用户名,查询并返回该用户的特定配置信息。
    如果找不到该用户,将返回默认配置。
    """
    return CONFIGS.get(name.lower(), CONFIGS["default"])


if __name__ == "__main__":
    mcp.run()

启动和调试

在终端中运行以下命令来启动服务:

SH 复制代码
uv run mcp dev main.py

启动成功后,你会看到类似以下的输出:

TXT 复制代码
Starting MCP inspector...
⚙️ Proxy server listening on localhost:6277
🔑 Session token: 174d31a94e2b0d1827180f2852c375c2694b4a179a919ac44edb76a3cadd0d63
   Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth

🚀 MCP Inspector is up and running at:
   http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=174d31a94e2b0d1827180f2852c375c2694b4a179a919ac44edb76a3cadd0d63

点开链接,会弹出一个 Web 界面,你可以在此进行资源的调试与查看。

DeepChat

在跟 AI 应用程序接入的时候需要将 mcp 的信息正确配置上,这里以 DeepChat 为例子来讲解如何配置

json 复制代码
{
  "mcpServers": {
    "test-server": {
      "command": "uv",
      "args": ["run", "--directory", "E:\\blog", "python", "main.py"],
      "env": {}
    }
  }
}

先看一下上面的 json,mcpServers 下可以支持多个 mcp 的 server,其中 test-server 是名称。剩下几个参数就比较好理解了,这里就不做阐述了。

不过这里特别需要提醒一下,关于 --directory 是非常必要的,python 中通常不会把包安装到全局环境下会使用虚拟环境来安装到当前目录下,而 --directory 则是在指定目录运行,同时结合 uv run 会自动激活虚拟环境。

点击新增然后把上面的内容粘贴即可。

在聊天界面输入 @ 之后选择 Resources 之后选择对应的资源,就完成了整个流程。

再聊资源模板

读到这里,你可能会产生一个核心疑问:我们定义的动态模板 configs://{name} 似乎在 DeepChat 的 @ 菜单里无法直接使用。这是否意味着模板资源没有用武之地?

这恰好触及了 MCP 标准与客户端应用实践的现状。MCP 协议定义了"能做什么"(比如支持模板),但将"如何做"(即提供怎样的 UI 来填充模板)的决定权交给了客户端。根据官方设计和社区实践,模板资源主要有三种使用路径:

  1. 客户端驱动(Client-Driven UI): 客户端应用可以解析资源模板的定义,然后自己构建交互界面,例如一个斜杠命令 /config <name> 或一个弹出表单,引导用户输入参数,最后在内部构造完整的 URI 并发起请求。
  2. 服务器辅助补全(Server-Assisted Completion): 这是一个更高级的模式。MCP 服务器可以为模板参数提供"自动补全"的建议。例如,当用户在客户端输入 @configs://a 时,客户端可以向服务器请求建议,服务器则根据 a 返回 alice 等可能的用户名。TypeScript SDK 对此有明确的支持。
  3. 工具化封装(Tool-Wrapping): 这是当前生态下最实用、最常见的最佳实践。我们将一个需要参数的资源查询逻辑,包装成一个 AI 可以调用的工具(Tool)。因为大语言模型非常擅长从自然语言中提取参数来调用工具,这完美地弥补了当前客户端 UI 在交互上的空白。

综上所述,模板资源并非没有作用,而是需要客户端应用的支持才能发挥其全部潜力。在下一篇《Tools 篇》中,我们将详细演示如何通过"工具化封装"的方式,让你定义的资源模板在任何 AI 应用 中都变得触手可及。

最后

本篇文章我们上手开发了一个包含静态和动态资源的 MCP Server,并成功在 DeepChat 中进行了调用。这只是一个简单的开始,后续章节会逐步拓展,例如使用 HTTP 协议替代 stdio 进行通信等。

下一节,我们将进入非常重要的《Tools 篇》,敬请期待。

相关推荐
剪刀石头布啊12 小时前
生成随机数,Math.random的使用
前端
剪刀石头布啊12 小时前
css外边距重叠问题
前端
剪刀石头布啊12 小时前
chrome单页签内存分配上限问题,怎么解决
前端
剪刀石头布啊12 小时前
css实现一个宽高固定百分比的布局的一个方式
前端
剪刀石头布啊12 小时前
js数组之快速组、慢数组、密集数组、稀松数组
前端
mango_mangojuice13 小时前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
程序员侠客行13 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple13 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
Days205013 小时前
简单处理接口返回400条数据本地数据分页加载
前端
PP东13 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable