构建 MCP 服务器:第 2 部分 — 使用资源模板扩展资源

该图像是使用 AI 图像创建程序创建的。

这个故事是在多位人工智能助手的帮助下写成的。

这是构建MCP 服务器教程(共四部分)的第二部分。在第一部分中我们使用基本资源创建了第一个 MCP 服务器。现在,我们将使用资源模板扩展服务器的功能。本文中的代码假设您从上次中断的地方继续学习。

什么是资源模板?

资源模板允许您使用 URI 模式定义动态资源。与具有固定 URI 的静态资源不同,模板允许您创建可根据参数生成 URI 和内容的资源。

可以将它们想象成 Web 框架中的 URL 模式,其中资源更具动态性,通常基于某些标签或 ID - 它们允许您使用单个定义匹配和处理整个资源系列。

为什么要使用资源模板?

当您需要处理动态数据、按需生成内容或创建基于参数的资源等时,资源模板非常强大。

以下是一些示例:

动态数据

“users://{userId}”  ->用户资料
“products://{sku}” ->产品信息

用户:“你能告诉我关于用户 12345 的情况吗?”
AI 助手:“正在查找用户 12345......他于 2023 年加入,已进行 50 次购买。”

按需生成内容

“reports://{year}/{month}”  ->月度报告
“analytics://{dateRange}” ->自定义分析

用户:“给我看看2024年3月的报告”
AI助手:“正在访问2024年3月的报告……与2月份相比,收入增长了15%。”

基于参数的资源

"search://{query}"  ->搜索结果
"filter://{type}/{value}" ->过滤数据

用户:“查找所有超过 1000 美元的交易”
AI 助手:“使用过滤资源...找到 23 笔符合您条件的交易。”

组织我们的代码

我们再来改进一下上篇文章中构建的代码结构,分离一些关注点。首先,把处理程序拆分成一个新文件 (handlers.ts),这样就不会太杂乱了:

// src/handlers.ts
import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
  ListResourceTemplatesRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { type Server } from "@modelcontextprotocol/sdk/server/index.js";

export const setupHandlers = (server: Server): void => {
  // List available resources when clients request them
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
    return {
      resources: [
        {
          uri: "hello://world",
          name: "Hello World Message",
          description: "A simple greeting message",
          mimeType: "text/plain",
        },
      ],
    };
  });
  // Return resource content when clients request it
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
    if (request.params.uri === "hello://world") {
      return {
        contents: [
          {
            uri: "hello://world",
            text: "Hello, World! This is my first MCP resource.",
          },
        ],
      };
    }
    throw new Error("Resource not found");
  });
};

更新我们的主要文件src/index.ts

// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { setupHandlers } from './handlers.js';

const server = new Server(
  {
    name: "hello-mcp",
    version: "1.0.0",
  },
  {
    capabilities: {
      resources: {},
    },
  }
);

setupHandlers(server);

// Start server using stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.info('{"jsonrpc": "2.0", "method": "log", "params": { "message": "Server running..." }}');

添加新资源

现在是时候添加我们的新资源模板了。

首先,让我们添加清单,以便 AI 助手知道它在那里。在资源清单(第一个参数为 的那个src/handlers.js之后添加以下代码。hello://worldListResourcesRequestSchema

export const setupHandlers = (server: Server): void => {
  // Existing "hello://world" resource listing here ...

  // Resource Templates
  server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
    resourceTemplates: [
      {
        greetings: {
          uriTemplate: 'greetings://{name}',
          name: 'Personal Greeting',
          description: 'A personalized greeting message',
          mimeType: 'text/plain',
        },
      },
    ],
  }));

  // Existing "hello://world" resource content here ... 
};

接下来我们可以添加内容处理程序。这不需要额外的请求处理程序。我们只需为这种格式的请求添加一个新的检查即可。

// Return resource content when clients request it
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  // ... Existing content handler code

  // Template-based resource code
  const greetingExp = /^greetings:\/\/(.+)$/;
  const greetingMatch = request.params.uri.match(greetingExp);
  if (greetingMatch) {
    const name = decodeURIComponent(greetingMatch[1]);
    return {
        contents: [
        {
            uri: request.params.uri,
            text: `Hello, ${name}! Welcome to MCP.`,
        },
      ],
    };
  }

  // ...
});

理解代码

处理程序组织

  • 我们已将处理程序移至单独的文件以便更好地组织
  • setupHandlers 函数封装了所有处理程序的设置
  • 主文件保持干净且专注

模板定义

  • 处理程序公开可用的模板 ListResourceTemplateRequestSchema
  • 模板名称格式遵循RFC 6570{text} (用于表达参数化的URL )
  • 模板包括名称和描述等元数据

模板处理

  • 处理程序现在检查模板匹配 ReadResourceRequestSchema
  • 我们使用正则表达式格式从 URI 中提取名称参数
  • 我们根据参数生成动态内容

使用检查器进行测试

在上一篇文章,我们讨论了如何使用MCP 检查器。现在启动检查器:

npx tsc
npx @modelcontextprotocol/inspector node build/index.js

测试我们上次创建的静态资源,以确保它仍然有效:

  • 点击“资源”选项卡
  • 找到并点击“Hello World Message”
  • 您应该会看到“Hello, World! 这是我的第一个 MCP 资源。”的消息。

测试模板:

  • 点击“资源模板”选项卡
  • 找到“个人问候语”
  • 输入名字“Alice”
{
  "contents": [
    {
      "uri": "greetings://Alice",
      "text": "Hello, Alice! Welcome to MCP."
    }
  ]
}

使用 Claude Desktop 进行测试

这次您不需要在 Claude 中更新任何内容,但您可能需要重新加载(同时,确保您已经使用构建了服务npx tsc)。

正如我上一篇文章所述,我为 Mac 使用的 Claude Desktop 似乎还不支持资源,但您可以在其他支持 MCP 的工具中尝试此操作,例如 Cline,您可能必须专门使用支持 MCP 的模型,例如 Anthropic 的 Sonnet 3.5。

尝试以下示例(响应可能有所不同):

静态资源:

用户: “问候语里有什么?” 
Claude: “问候语是:‘来自 MCP 的问候!这是您的第一个资源。’”

模板资源:

用户: “你能给爱丽丝找个问候语吗?”
克劳德: “我去看看个性化问候语……上面写着:‘你好,爱丽丝!欢迎来到 MCP。’”

列出可用资源:

用户:“有哪些资源和模板可用?” 
Claude:“服务器提供:
1. 静态‘问候语’资源
2. 可以为任何姓名创建自定义问候语的‘个人问候语’模板”

下一步是什么?

第 3 部分中,我们将:

  • 通过将资源和模板分离到各自的文件中,进一步改善代码组织
  • 了解 MCP 提示以及它们与资源的区别
  • 为我们的服务器添加提示功能
  • 看看提示如何增强我们的问候功能

第 4 部分将通过向我们的服务器添加工具来完成我们的课程。

资源和其他阅读材料: