构建 MCP 服务器:第 3 部分 — 添加提示

这是我们构建 MCP 服务器的四部分教程的第三部分。在第一部分中,我们使用基本资源创建了第一个MCP 服务器;在第二部分中,我们添加了资源模板并改进了代码组织。现在,我们将进一步重构代码并添加提示功能

什么是 MCP 提示?

MCP 中的提示符是服务器提供的结构化模板,用于标准化与语言模型的交互。与提供数据的资源或执行操作的工具不同,提示符定义了可重用的消息序列和工作流,有助于以一致、可预测的方式指导 LLM 行为。它们可以接受参数来自定义交互,同时保持标准化的结构。如果您曾经研究过提示符工程,那么您可能对提示符的概念已经有了相当的了解。在 MCP 服务器中创建这些提示符,使我们能够为我们认为最有用的提示符创建一个空间,使其易于重用甚至共享。想象一下去餐厅,提示符就像一个菜单项,您可以从中挑选并提供给服务员。有时,您可以通过要求添加或删除某些菜品或以特定方式烹饪来自定义菜单项。以这种方式提供的提示符具有类似的功能。

为什么要使用提示?

提示有助于为 LLM 交互创建一致、可重复使用的模式。以下是一些实际示例:

代码审查提示

"name" -> code-review
Please review the following {{language}} code focusing on {{focusAreas}} for the following block of code:
```{{language}}
{{codeBlock}}
```

用户:请检查以下关注安全性和性能的 Python 代码:
“Python
...代码

数据分析提示

"name" -> analyze-sales-data
Analyze {{timeframe}} sales data focusing on {{metrics}}User: Analyze Q1 sales data focusing on revenue and growth

内容生成提示

"name" -> generate-email
Generate a {{tone}} {{type}} email for {{context}}

用户:生成一封正式的支持电子邮件,以向 Bob's Barbecue LLC 提出退款请求。

代码组织

在第二部分,我们从 index.ts 中抽象出了处理程序代码,并将其放入 handlers.ts 文件中。这个文件可能会变得过大。我们应该将处理程序代码组织到各个模块中:

// src/resources.ts
export const resources = [
  {
    uri: "hello://world",
    name: "Hello World Message",
    description: "A simple greeting message",
    mimeType: "text/plain",
  },
];

export const resourceHandlers = {
  "hello://world": () => ({
    contents: [
      {
        uri: "hello://world",
        text: "Hello, World! This is my first MCP resource.",
      },
    ],
  }),
};
// src/resource-templates.ts
export const resourceTemplates = [
  {
    uriTemplate: "greetings://{name}",
    name: "Personal Greeting",
    description: "A personalized greeting message",
    mimeType: "text/plain",
  },
];

const greetingExp = /^greetings:\/\/(.+)$/;
const greetingMatchHandler =
  (uri: string, matchText: RegExpMatchArray) => () => {
    const name = decodeURIComponent(matchText[1]);
    return {
      contents: [
        {
          uri,
          text: `Hello, ${name}! Welcome to MCP.`,
        },
      ],
    };
  };
export const getResourceTemplate = (uri: string) => {
  const greetingMatch = uri.match(greetingExp);
  if (greetingMatch) return greetingMatchHandler(uri, greetingMatch);
};

更新我们的处理程序

// src/handlers.ts
import {
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { type Server } from "@modelcontextprotocol/sdk/server/index.js";
import { resourceHandlers, resources } from "./resources.js";
import {
  getResourceTemplate,
  resourceTemplates,
} from "./resource-templates.js";

export const setupHandlers = (server: Server): void => {
  // List available resources when clients request them
  server.setRequestHandler(
    ListResourcesRequestSchema,
    () => ({ resources }),
  );
  // Resource Templates
  server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
    resourceTemplates,
  }));
  // Return resource content when clients request it
  server.setRequestHandler(ReadResourceRequestSchema, (request) => {
    const { uri } = request.params ?? {};
    const resourceHandler =
      resourceHandlers[uri as keyof typeof resourceHandlers];
    if (resourceHandler) return resourceHandler();
    const resourceTemplateHandler = getResourceTemplate(uri);
    if (resourceTemplateHandler) return resourceTemplateHandler();
    throw new Error("Resource not found");
  });
};

添加提示

现在添加我们的新提示功能:

// src/prompts.ts
export const prompts = {
  "create-greeting": {
    name: "create-greeting",
    description: "Generate a customized greeting message",
    arguments: [
        { 
            name: "name",
            description: "Name of the person to greet",
            required: true,
        },
        {
            name: "style",
            description: "The style of greeting, such a formal, excited, or casual. If not specified casual will be used"
        }
    ],
  },
};

export const promptHandlers = {
  "create-greeting": ({ name, style = "casual" }: { name: string, style?: string }) => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `Please generate a greeting in ${style} style to ${name}.`,
          },
        },
      ],
    };
  },
};

将我们的新提示处理程序添加到处理程序文件中:

// src/handlers.ts
import {
  GetPromptRequestSchema,
  ListPromptsRequestSchema,
  // ... other imports
} from "@modelcontextprotocol/sdk/types.js";
// ... other imports
import { promptHandlers, prompts } from "./prompts.js";

export const setupHandlers = (server: Server): void => {
 
  // ... Other resource handlers here

  // Prompts
  server.setRequestHandler(ListPromptsRequestSchema, () => ({
    prompts: Object.values(prompts),
  }));
  server.setRequestHandler(GetPromptRequestSchema, (request) => {
    const { name, arguments: args } = request.params;
    const promptHandler = promptHandlers[name as keyof typeof promptHandlers];
    if (promptHandler) return promptHandler(args as { name: string, style?: string });
    throw new Error("Prompt not found");
  });
};

最后,我们需要更新服务器初始化

// 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: {
      prompts: {}, // <-- Add prompts
      resources: {},
    },
  },
);

setupHandlers(server);

// ... remaining code

理解代码

模块组织

  • 资源和模板已放置在各自的模块中
  • 提示清晰地分开
  • 处理程序现在充当路由层

提示结构

  • 每个提示都有名称、描述和参数(如果需要)
  • 参数描述提示的预期输入
  • 处理程序生成结构化消息以提示目标 AI

消息序列

  • 提示返回消息数组
  • 消息具有角色(“用户”或“助手”)
  • 内容可以包括多步骤工作流的初始请求和后续响应(请注意,目前多步骤工作流的支持有限)

使用检查器进行测试

启动检查器:

npx @modelcontextprotocol/inspector node build/index.js

测试提示:

  • 点击“提示”选项卡
  • 找到“create-greeting”
  • 尝试不同的参数组合:
名字:“爱丽丝”,风格:“兴奋”
{
  "messages": [
    {
      "role": "user",
      "content": {
        "type": "text",
        "text": "Please generate a greeting in excited style to Alice."
      }
    }
  ]
}

使用 Claude Desktop 进行测试

尝试以下示例:

基本提示:

1:打开Claude桌面
假设:

2:与我们添加资源的方式类似,点击“从 MCP 附加”

3:在模态弹出窗口中,点击“选择并集成”,然后从“hello-mcp”下的列表中选择“create-greeting”提示

4:现在,只需输入姓名即可进行测试。在姓名字段中输入类似“John”的内容,然后点击“提交”。

5:您将看到一个“create-greeting”附件。点击它查看其中的内容。

6:您将看到这里有一个给克劳德的提示,上面写着“请向约翰致以随意的问候”。

7:无需输入任何其他提示,只需单击聊天框右上角的提交箭头即可

8:您将看到类似“嗨,约翰!你今天过得怎么样?”的回复。

样式提示:

1:现在,尝试使用不同的特定样式创建问候语。打开“从 MCP 附加”对话框,然后再次选择“创建问候语”提示。这次,我们可以添加名称“Alice”和样式“正式”,然后提交聊天。再次使用箭头键,或者直接按 Enter 键也可以,我还没试过。

2:这一次,您可能会看到返回如下消息:

亲爱的爱丽丝,

祝您一切安好。谨致以最诚挚的问候。

谨致问候,
克劳德

下一步是什么?

第 4 部分中,我们将:

  • 了解MCP 工具及其与提示的区别
  • 为我们的服务器添加工具功能
  • 了解工具如何提供动态功能
  • 使用所有主要的 MCP 功能完成我们的问候服务器

资料来源及其他阅读材料: