MCP服务器开发指南 (2)

在第一部分中,我们创建了一个具有基本资源的第一个MCP服务器。现在我们将使用资源模板扩展我们的服务器功能。

MCP服务器开发指南 (2)

这是关于构建MCP服务器的四部分教程中的第二部分。在第一部分中,我们创建了一个具有基本资源的第一个MCP服务器。现在我们将使用资源模板扩展我们的服务器功能。本文中的代码假设你已经从上一部分继续进行。

1、什么是资源模板?

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

你可以将它们视为Web框架中的URL模式,其中资源更动态且通常基于某些标签或ID——它们让您使用单个定义匹配和处理整个资源家族。

2、为什么使用资源模板?

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

以下是一些示例:

动态数据:

"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笔符合您条件的交易。”

3、组织我们的代码

让我们通过分离一些关注点来改进我们在上一篇文章中构建的代码结构我们建立的代码。首先,让我们将处理器移到一个新的文件(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 => {  
  // 当客户端请求时列出可用资源  
  server.setRequestHandler(ListResourcesRequestSchema, async () => {  
    return {  
      resources: [  
        {  
          uri: "hello://world",  
          name: "Hello World Message",  
          description: "A simple greeting message",  
          mimeType: "text/plain",  
        },  
      ],  
    };  
  });  
  // 当客户端请求时返回资源内容  
  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);  
  
// 使用stdio传输启动服务器  
const transport = new StdioServerTransport();  
await server.connect(transport);  
console.info('{"jsonrpc": "2.0", "method": "log", "params": { "message": "Server running..." }}');

4、添加新的资源

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

首先,让我们添加我们的列表,以便AI助手知道它的存在。在 src/handlers.js 中,在 hello://world 资源列表之后(第一个参数为 ListResourcesRequestSchema 的那个)

export const setupHandlers = (server: Server): void => {  
  // 这里有现有的 "hello://world" 资源列表 ...  
  
  // 资源模板  
  server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({  
    resourceTemplates: [  
      {  
        greetings: {  
          uriTemplate: 'greetings://{name}',  
          name: 'Personal Greeting',  
          description: 'A personalized greeting message',  
          mimeType: 'text/plain',  
        },  
      },  
    ],  
  }));  
  
  // 这里有现有的 "hello://world" 资源内容 ...   
};

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

// 当客户端请求时返回资源内容  
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {  
  // ... 现有的内容处理器代码  
  
  // 基于模板的资源代码  
  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中提取名称参数。
  • 我们根据参数生成动态内容。

5、使用检查器测试

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

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

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

  • 点击“资源”标签
  • 查找并点击“Hello World Message”
  • 您应该看到“Hello, World! This is my first MCP resource.”消息

测试模板:

  • 点击“资源模板”标签
  • 查找“Personal Greeting”
  • 输入名字“Alice”
{  
  "contents": [  
    {  
      "uri": "greetings://Alice",  
      "text": "Hello, Alice! Welcome to MCP."  
    }  
  ]  
}

6、使用Claude桌面测试

这次你可能不需要在Claude中进行任何更新,但可能需要重新加载(还要确保您已使用 npx tsc 构建服务)。

正如我上次所述,我的Mac上的Claude桌面似乎还不支持资源,但你可能可以在其他支持MCP的工具中尝试,例如Cline,你可能需要特别使用一个了解MCP的模型,比如Anthropic的Sonnet 3.5。

尝试这些示例(响应可能会有所不同):

静态资源:

用户:“问候消息里有什么?”  
Claude:“问候消息说:‘Hello from MCP! This is your first resource.’”

模板资源:

用户:“你能为Alice获取问候吗?”  
Claude:“我会检查个性化问候... 它说:‘Hello, Alice! Welcome to MCP.’”

列出可用资源:

用户:“有哪些资源和模板可用?”  
Claude:“服务器提供:  
1. 一个静态的‘Greeting Message’资源  
2. 一个‘Personal Greeting’模板,可以为任何名字创建定制问候”

7、结束语

在第三部分中,我们将:

  • 通过将资源和模板分离到自己的文件中进一步改进代码组织。
  • 学习MCP提示及其与资源的区别。
  • 向我们的服务器添加提示功能。
  • 看看提示如何增强我们的问候功能。

第四部分将在添加服务器工具后完成我们的课程。


原文链接:Building MCP Servers: Part 2 — Extending Resources with Resource Templates

汇智网翻译整理,转载请标明出处