首页 › tutorials › MCP全原语实战:Tool到Elicitation

MCP全原语实战:从Tool到Elicitation五大能力深度开发

概述

MCP(Model Context Protocol)远不止"注册几个工具函数"这么简单。协议定义了五大服务端原语——Tools、Resources、Prompts、Sampling、Elicitation——每一种都解决不同的人机交互场景。本教程从零构建一个完整的 MCP Server,逐步解锁全部五种原语能力,适合已有 MCP 基础、想深入掌握协议全貌的开发者。

学习目标:掌握 MCP 五大原语的设计意图、实现方式、最佳实践,构建一个生产级多原语 MCP Server。

适用人群:已了解 MCP 基本概念、有 TypeScript 或 Python 开发经验的开发者。

前置条件


第一部分:理解MCP原语全景

为什么需要五种原语?

大多数 MCP 教程只讲 Tools——让 AI 调用函数执行操作。但真实的 AI 应用场景远不止"调用函数":

graph TB
    subgraph "MCP 五大原语"
        T[Tools<br/>执行操作/计算]
        R[Resources<br/>暴露可读数据]
        P[Prompts<br/>提供提示模板]
        S[Sampling<br/>请求LLM推理]
        E[Elicitation<br/>请求用户输入]
    end

    subgraph "使用场景"
        S1[查询数据库 → Tool]
        S2[读取文件内容 → Resource]
        S3[代码审查模板 → Prompt]
        S4[智能分类/摘要 → Sampling]
        S5[缺失参数确认 → Elicitation]
    end

    T --> S1
    R --> S2
    P --> S3
    S --> S4
    E --> S5
原语 方向 核心用途 类比
Tools Client → Server 让 AI 执行操作 HTTP POST
Resources Client → Server 让 AI 读取数据 HTTP GET
Prompts Client → Server 提供预定义提示模板 HTTP GET (模板)
Sampling Server → Client Server 请求 LLM 推理 回调/RPC
Elicitation Server → Client → User Server 请求用户确认 对话框弹窗

关键区别:Tools 和 Resources 是"AI 主动调",Sampling 和 Elicitation 是"Server 反向请求"。


第二部分:项目初始化

步骤一:创建项目骨架

mkdir mcp-full-primitives && cd mcp-full-primitives
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

创建 tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

项目结构:

mcp-full-primitives/
├── src/
│   ├── index.ts          # 主入口:注册所有原语
│   ├── tools.ts          # Tools 注册
│   ├── resources.ts      # Resources 注册
│   ├── prompts.ts        # Prompts 注册
│   ├── sampling.ts       # Sampling 处理器
│   ├── elicitation.ts    # Elicitation 处理器
│   └── data/             # 示例数据
│       └── knowledge.json
├── package.json
└── tsconfig.json

步骤二:创建示例数据

创建 src/data/knowledge.json:

{
  "articles": [
    {
      "id": "mcp-001",
      "title": "MCP 架构设计指南",
      "content": "MCP 采用客户端-服务器架构...",
      "tags": ["architecture", "guide"],
      "updated": "2026-05-20"
    },
    {
      "id": "mcp-002",
      "title": "Streamable HTTP 传输层实战",
      "content": "Streamable HTTP 是 MCP 2025 规范新增的传输方式...",
      "tags": ["transport", "http"],
      "updated": "2026-05-22"
    },
    {
      "id": "mcp-003",
      "title": "MCP 安全最佳实践",
      "content": "生产环境 MCP Server 需要考虑...",
      "tags": ["security", "production"],
      "updated": "2026-05-25"
    }
  ],
  "status": "active",
  "version": "2.1.0"
}

第三部分:Tools——执行操作的核心原语

Tools 是最常用的原语。AI 根据用户请求自动选择并调用合适的 Tool。

步骤三:实现 Tools

创建 src/tools.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// 模拟数据库
interface Article {
  id: string;
  title: string;
  content: string;
  tags: string[];
  updated: string;
}

let articles: Article[] = [
  {
    id: "mcp-001",
    title: "MCP 架构设计指南",
    content: "MCP 采用客户端-服务器架构,通过 JSON-RPC 2.0 通信...",
    tags: ["architecture", "guide"],
    updated: "2026-05-20",
  },
  {
    id: "mcp-002",
    title: "Streamable HTTP 传输层实战",
    content: "Streamable HTTP 是 MCP 2025 规范新增的传输方式...",
    tags: ["transport", "http"],
    updated: "2026-05-22",
  },
];

export function registerTools(server: McpServer) {
  // Tool 1: 搜索文章
  server.tool(
    "search_articles",
    "根据关键词搜索知识库中的文章,返回匹配的文章列表",
    {
      keyword: z.string().describe("搜索关键词"),
      limit: z.number().optional().default(5).describe("返回结果数量上限"),
    },
    async ({ keyword, limit }) => {
      const results = articles.filter(
        (a) =>
          a.title.includes(keyword) ||
          a.content.includes(keyword) ||
          a.tags.includes(keyword)
      );
      const limited = results.slice(0, limit);
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(
              {
                total: results.length,
                returned: limited.length,
                articles: limited.map((a) => ({
                  id: a.id,
                  title: a.title,
                  tags: a.tags,
                })),
              },
              null,
              2
            ),
          },
        ],
      };
    }
  );

  // Tool 2: 创建文章(带进度通知)
  server.tool(
    "create_article",
    "创建一篇新文章到知识库",
    {
      title: z.string().describe("文章标题"),
      content: z.string().describe("文章内容"),
      tags: z.array(z.string()).optional().default([]).describe("标签列表"),
    },
    async ({ title, content, tags }, { sendNotification }) => {
      const id = `mcp-${String(articles.length + 1).padStart(3, "0")}`;
      const article: Article = {
        id,
        title,
        content,
        tags,
        updated: new Date().toISOString().split("T")[0],
      };

      // 发送进度通知(Notification 原语的典型用法)
      await sendNotification({
        method: "notifications/progress",
        params: {
          progress: 50,
          total: 100,
          progressToken: "create-article",
        },
      });

      articles.push(article);

      await sendNotification({
        method: "notifications/progress",
        params: {
          progress: 100,
          total: 100,
          progressToken: "create-article",
        },
      });

      return {
        content: [
          {
            type: "text",
            text: `文章创建成功!ID: ${id}, 标题: ${title}`,
          },
        ],
      };
    }
  );

  // Tool 3: 删除文章
  server.tool(
    "delete_article",
    "根据ID删除知识库中的文章",
    {
      article_id: z.string().describe("要删除的文章ID"),
    },
    async ({ article_id }) => {
      const index = articles.findIndex((a) => a.id === article_id);
      if (index === -1) {
        return {
          content: [{ type: "text", text: `错误:未找到ID为 ${article_id} 的文章` }],
          isError: true,
        };
      }
      const removed = articles.splice(index, 1)[0];
      return {
        content: [{ type: "text", text: `已删除文章:${removed.title}` }],
      };
    }
  );
}

// 导出供 Resources 使用
export { articles };
export function getArticles() {
  return articles;
}

关键要点:

常见问题

Q: Tool 的描述应该写多长? A: 一句话说明用途即可。AI 根据描述判断是否调用,描述不清晰会导致误调用或漏调用。避免写实现细节,聚焦于"什么场景用"和"返回什么"。

Q: 参数用 .optional() 和 .default() 有什么区别? A: .optional() 允许 AI 不传该参数(值为 undefined),.default() 在 AI 不传时自动使用默认值。推荐对可选项使用 .default() 以减少 AI 决策负担。


第四部分:Resources——暴露可读数据

Resources 是 MCP 中最被低估的原语。它让 Server 以 URI 形式暴露数据,AI 可以主动"浏览"而不必通过 Tool 查询。

Resources vs Tools 的核心区别

维度 Resources Tools
语义 读取数据(幂等) 执行操作(可能有副作用)
寻址 URI(如 knowledge://articles/mcp-001) 名称 + 参数
AI 行为 主动浏览、订阅更新 按需调用
缓存 客户端可缓存 通常不缓存

步骤四:实现 Resources

创建 src/resources.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { getArticles } from "./tools.js";

export function registerResources(server: McpServer) {
  // Resource 1: 静态资源 - 知识库概览
  server.resource(
    "knowledge-overview",
    "knowledge://overview",
    async (uri) => {
      const articles = getArticles();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify({
              totalArticles: articles.length,
              allTags: [...new Set(articles.flatMap((a) => a.tags))],
              lastUpdated: articles.reduce(
                (latest, a) => (a.updated > latest ? a.updated : latest),
                ""
              ),
            }),
          },
        ],
      };
    }
  );

  // Resource 2: 动态资源模板 - 按ID获取文章详情
  server.resource(
    "article-detail",
    new URL("knowledge://articles/{id}"),
    async (uri, { id }) => {
      const articles = getArticles();
      const article = articles.find((a) => a.id === id);

      if (!article) {
        return {
          contents: [
            {
              uri: uri.href,
              mimeType: "text/plain",
              text: `未找到ID为 ${id} 的文章`,
            },
          ],
        };
      }

      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(article, null, 2),
          },
        ],
      };
    }
  );

  // Resource 3: 按标签筛选的资源模板
  server.resource(
    "articles-by-tag",
    new URL("knowledge://tags/{tag}"),
    async (uri, { tag }) => {
      const articles = getArticles();
      const filtered = articles.filter((a) => a.tags.includes(tag));

      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(
              {
                tag,
                count: filtered.length,
                articles: filtered.map((a) => ({ id: a.id, title: a.title })),
              },
              null,
              2
            ),
          },
        ],
      };
    }
  );
}

关键要点:

资源列表与发现

MCP Server 会自动处理 resources/list 和 resources/templates/list 请求,客户端可以浏览所有可用资源。这意味着在 Claude Code 中,AI 能自动"看到"你的 Resources 并决定何时读取。


第五部分:Prompts——预定义提示模板

Prompts 让你为 AI 提供预定义的、可参数化的提示模板。用户可以通过 Claude Code 的 / 命令选择使用。

步骤五:实现 Prompts

创建 src/prompts.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { getArticles } from "./tools.js";

export function registerPrompts(server: McpServer) {
  // Prompt 1: 文章审查提示
  server.prompt(
    "review-article",
    "对指定文章进行专业审查,给出改进建议",
    {
      article_id: z.string().describe("要审查的文章ID"),
      focus_area: z
        .enum(["accuracy", "completeness", "readability", "all"])
        .optional()
        .default("all")
        .describe("审查重点领域"),
    },
    async ({ article_id, focus_area }) => {
      const articles = getArticles();
      const article = articles.find((a) => a.id === article_id);

      if (!article) {
        return {
          messages: [
            {
              role: "user",
              content: {
                type: "text",
                text: `未找到ID为 ${article_id} 的文章,请检查ID是否正确。`,
              },
            },
          ],
        };
      }

      const focusInstructions: Record<string, string> = {
        accuracy: "重点检查技术准确性,验证所有技术声明",
        completeness: "重点检查内容完整性,是否有遗漏的关键知识点",
        readability: "重点检查可读性,建议改善结构和表达方式",
        all: "全面审查:准确性、完整性、可读性、结构、示例质量",
      };

      return {
        messages: [
          {
            role: "user",
            content: {
              type: "text",
              text: [
                `请对以下文章进行专业审查。`,
                ``,
                `## 审查重点`,
                `${focusInstructions[focus_area]}`,
                ``,
                `## 文章内容`,
                `标题:${article.title}`,
                `标签:${article.tags.join(", ")}`,
                `最后更新:${article.updated}`,
                ``,
                `${article.content}`,
                ``,
                `## 审查要求`,
                `1. 给出总体评分(1-10)`,
                `2. 列出具体问题及改进建议`,
                `3. 提供修改后的优化版本`,
              ].join("\n"),
            },
          },
        ],
      };
    }
  );

  // Prompt 2: 生成文章大纲
  server.prompt(
    "generate-outline",
    "根据主题生成知识库文章的结构大纲",
    {
      topic: z.string().describe("文章主题"),
      depth: z
        .enum(["beginner", "intermediate", "advanced"])
        .optional()
        .default("intermediate")
        .describe("目标读者水平"),
      include_code: z
        .boolean()
        .optional()
        .default(true)
        .describe("是否包含代码示例"),
    },
    async ({ topic, depth, include_code }) => {
      const depthLabels = {
        beginner: "入门级",
        intermediate: "进阶级",
        advanced: "专家级",
      };

      return {
        messages: [
          {
            role: "user",
            content: {
              type: "text",
              text: [
                `请为以下主题生成一篇${depthLabels[depth]}技术文章的详细大纲。`,
                ``,
                `主题:${topic}`,
                `目标读者:${depthLabels[depth]}开发者`,
                `${include_code ? "需要包含代码示例" : "纯概念讲解,不需要代码"}`,
                ``,
                `大纲格式要求:`,
                `- 使用三级标题结构(H1 > H2 > H3)`,
                `- 每个章节附简要说明(1-2句话)`,
                `- 标注预计字数`,
                `- ${include_code ? "标注哪些章节需要代码示例" : ""}`,
              ].join("\n"),
            },
          },
        ],
      };
    }
  );
}

关键要点:


第六部分:Sampling——Server反向请求LLM

Sampling 是 MCP 中最独特的原语:Server 主动请求 Client 端的 LLM 进行推理。这让 Server 可以在执行过程中"借助 AI 大脑"做决策。

典型使用场景

步骤六:实现 Sampling

创建 src/sampling.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { getArticles } from "./tools.js";
import type { CreateMessageRequest } from "@modelcontextprotocol/sdk/types.js";

export function registerSamplingTools(server: McpServer) {
  // 使用 Sampling 的 Tool:智能分析知识库
  server.tool(
    "analyze_knowledge_base",
    "使用AI分析知识库的整体状况,生成智能报告",
    {
      analysis_type: z
        .enum(["summary", "gaps", "trends"])
        .describe("分析类型:summary=概要, gaps=知识盲区, trends=趋势分析"),
    },
    async ({ analysis_type }, { session }) => {
      const articles = getArticles();

      // 检查客户端是否支持 Sampling
      if (!session?.capabilities?.sampling) {
        return {
          content: [
            {
              type: "text",
              text: "当前客户端不支持 Sampling 能力。请使用支持 Sampling 的客户端。",
            },
          ],
          isError: true,
        };
      }

      // 构造 Sampling 请求
      const promptMap = {
        summary: `请用中文对以下知识库内容进行总结概述:\n\n${JSON.stringify(articles, null, 2)}`,
        gaps: `分析以下知识库中的文章,找出知识盲区和缺失的主题领域:\n\n${JSON.stringify(
          articles.map((a) => ({ id: a.id, title: a.title, tags: a.tags })),
          null,
          2
        )}`,
        trends: `分析以下文章的标签和更新时间,识别知识库的发展趋势:\n\n${JSON.stringify(
          articles.map((a) => ({ title: a.title, tags: a.tags, updated: a.updated })),
          null,
          2
        )}`,
      };

      try {
        // 向客户端的 LLM 发送 Sampling 请求
        const result = await session.request(
          {
            method: "sampling/createMessage",
            params: {
              messages: [
                {
                  role: "user",
                  content: {
                    type: "text",
                    text: promptMap[analysis_type],
                  },
                },
              ],
              maxTokens: 1000,
              systemPrompt: "你是一位知识管理专家,擅长分析技术文档库。",
            },
          },
          CreateMessageResultSchema
        );

        return {
          content: [
            {
              type: "text",
              text: `## AI 分析报告\n\n${
                result.content.type === "text" ? result.content.text : "分析完成"
              }`,
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Sampling 请求失败:${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        };
      }
    }
  );
}

注意:Sampling 的实际代码需要从 SDK 导入 CreateMessageResultSchema。在 TypeScript SDK v2 中,完整的 Sampling 集成如下:

import { CreateMessageResultSchema } from "@modelcontextprotocol/sdk/types.js";

Sampling 工作流程

sequenceDiagram
    participant User as 用户
    participant CC as Claude Code<br/>(MCP Client)
    participant Server as MCP Server

    User->>CC: "分析知识库盲区"
    CC->>Server: tools/call (analyze_knowledge_base)

    Note over Server: 检测到需要AI分析
    Server->>CC: sampling/createMessage<br/>(请求LLM推理)
    CC->>CC: 调用内部LLM生成分析
    CC->>Server: 返回LLM分析结果

    Note over Server: 整合分析结果
    Server->>CC: 返回最终Tool结果
    CC->>User: 展示分析报告

安全要点:


第七部分:Elicitation——Server请求用户输入

Elicitation 是 MCP 2026 规范中最新的原语。它让 Server 能在 Tool 执行过程中暂停并请求用户输入,实现真正的 Human-in-the-Loop。

为什么需要 Elicitation?

想象你的 Tool 要删除一个重要资源——你可能想在执行前确认。或者 Tool 检测到参数不完整——你可能想追问用户。Elicitation 正是为此而生。

步骤七:实现 Elicitation

创建 src/elicitation.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { getArticles } from "./tools.js";

export function registerElicitationTools(server: McpServer) {
  // 使用 Elicitation 的 Tool:安全删除文章(需用户确认)
  server.tool(
    "safe_delete_article",
    "安全删除文章(需要用户二次确认)",
    {
      article_id: z.string().describe("要删除的文章ID"),
    },
    async ({ article_id }, { session }) => {
      const articles = getArticles();
      const article = articles.find((a) => a.id === article_id);

      if (!article) {
        return {
          content: [
            { type: "text", text: `未找到ID为 ${article_id} 的文章` },
          ],
          isError: true,
        };
      }

      // 检查 Elicitation 能力
      if (!session?.capabilities?.elicitation) {
        // 降级方案:不支持 Elicitation 时直接拒绝
        return {
          content: [
            {
              type: "text",
              text: `安全删除需要客户端支持 Elicitation。文章 "${article.title}" 未被删除。`,
            },
          ],
        };
      }

      // 向用户发送确认请求
      try {
        const elicitResult = await session.request(
          {
            method: "elicitation/create",
            params: {
              message: `确定要删除文章「${article.title}」吗?此操作不可撤销。`,
              requestedSchema: {
                type: "object",
                properties: {
                  confirmed: {
                    type: "boolean",
                    title: "确认删除",
                    description: "勾选以确认删除此文章",
                  },
                  reason: {
                    type: "string",
                    title: "删除原因(可选)",
                    description: "请简要说明删除原因",
                  },
                },
                required: ["confirmed"],
              },
            },
          },
          ElicitResultSchema
        );

        if (!elicitResult.content?.confirmed) {
          return {
            content: [
              { type: "text", text: `用户取消了删除操作。文章 "${article.title}" 保持不变。` },
            ],
          };
        }

        // 用户确认后执行删除
        const index = articles.findIndex((a) => a.id === article_id);
        if (index !== -1) {
          articles.splice(index, 1);
        }

        const reason = elicitResult.content?.reason
          ? `\n删除原因:${elicitResult.content.reason}`
          : "";

        return {
          content: [
            {
              type: "text",
              text: `已成功删除文章「${article.title}」。${reason}`,
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `确认请求被拒绝或出错,文章未被删除。`,
            },
          ],
        };
      }
    }
  );

  // 使用 Elicitation 的 Tool:交互式创建文章
  server.tool(
    "interactive_create",
    "交互式创建文章(自动补充缺失信息)",
    {
      title: z.string().describe("文章标题"),
      content: z.string().optional().describe("文章内容(可选)"),
    },
    async ({ title, content }, { session }) => {
      // 如果内容缺失,通过 Elicitation 请求用户补充
      if (!content && session?.capabilities?.elicitation) {
        try {
          const result = await session.request(
            {
              method: "elicitation/create",
              params: {
                message: `正在创建文章「${title}」,但缺少文章内容。请补充:`,
                requestedSchema: {
                  type: "object",
                  properties: {
                    content: {
                      type: "string",
                      title: "文章内容",
                      description: "请输入文章正文内容",
                    },
                    tags: {
                      type: "string",
                      title: "标签(逗号分隔)",
                      description: "如: architecture, guide",
                    },
                  },
                  required: ["content"],
                },
              },
            },
            ElicitResultSchema
          );

          const finalContent = result.content?.content || "(无内容)";
          const tags = result.content?.tags
            ? result.content.tags.split(",").map((t: string) => t.trim())
            : [];

          // 现在有完整数据了,创建文章
          const articles = getArticles();
          const id = `mcp-${String(articles.length + 1).padStart(3, "0")}`;
          articles.push({
            id,
            title,
            content: finalContent,
            tags,
            updated: new Date().toISOString().split("T")[0],
          });

          return {
            content: [
              { type: "text", text: `交互式创建成功!文章ID: ${id}` },
            ],
          };
        } catch {
          return {
            content: [
              { type: "text", text: "用户取消了内容补充,文章未创建。" },
            ],
          };
        }
      }

      // 有内容时直接创建
      if (content) {
        const articles = getArticles();
        const id = `mcp-${String(articles.length + 1).padStart(3, "0")}`;
        articles.push({
          id,
          title,
          content,
          tags: [],
          updated: new Date().toISOString().split("T")[0],
        });

        return {
          content: [
            { type: "text", text: `文章创建成功!文章ID: ${id}` },
          ],
        };
      }

      return {
        content: [
          {
            type: "text",
            text: "无法创建文章:缺少内容且客户端不支持交互式输入。",
          },
        ],
        isError: true,
      };
    }
  );
}

注意:ElicitResultSchema 需要从 SDK 类型中导入。在 TypeScript SDK 的最新版本中:

import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";

如果 SDK 版本尚未包含此 Schema,可以使用自定义 Zod schema:

import { z } from "zod";

const ElicitResultSchema = z.object({
  action: z.enum(["accept", "decline", "cancel"]),
  content: z.record(z.unknown()).optional(),
});

第八部分:组装主入口

步骤八:整合所有原语

创建 src/index.ts:

#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerTools } from "./tools.js";
import { registerResources } from "./resources.js";
import { registerPrompts } from "./prompts.js";
import { registerSamplingTools } from "./sampling.js";
import { registerElicitationTools } from "./elicitation.js";

async function main() {
  // 创建 MCP Server 实例
  const server = new McpServer({
    name: "knowledge-base-full",
    version: "1.0.0",
  });

  // 注册五大原语
  registerTools(server);           // Tools:搜索、创建、删除
  registerResources(server);       // Resources:知识库数据暴露
  registerPrompts(server);         // Prompts:审查、大纲模板
  registerSamplingTools(server);   // Sampling:智能分析
  registerElicitationTools(server); // Elicitation:安全确认

  // 启动 stdio 传输
  const transport = new StdioServerTransport();
  await server.connect(transport);

  console.error("Knowledge Base MCP Server 已启动(全原语模式)");
}

main().catch((error) => {
  console.error("致命错误:", error);
  process.exit(1);
});

在 package.json 中添加构建和启动脚本:

{
  "name": "mcp-full-primitives",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc && node dist/index.js"
  }
}

构建并测试:

npm run build
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | npm start

预期输出包含 capabilities 字段,其中 tools、resources、prompts 均为可用状态。


第九部分:注册到 Claude Code

步骤九:配置 MCP Server

# 方式一:使用 claude mcp add 命令
claude mcp add knowledge-base -- node /path/to/mcp-full-primitives/dist/index.js

# 方式二:直接编辑配置文件
# 全局配置:~/.claude/settings.json
# 项目配置:.claude/settings.json

在 .claude/settings.json 中添加:

{
  "mcpServers": {
    "knowledge-base": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-full-primitives/dist/index.js"]
    }
  }
}

验证注册成功

启动 Claude Code 后,输入 /mcp 命令查看已注册的 MCP Server:

claude
> /mcp

你应该能看到 knowledge-base Server,以及它暴露的所有 Tools、Resources、Prompts。

测试各原语

# 测试 Tool
> 搜索知识库中关于 HTTP 的文章

# 测试 Resource
> 读取 knowledge://overview 资源

# 测试 Prompt
> 使用 review-article 提示模板审查文章 mcp-001

# 测试 Sampling
> 分析知识库的知识盲区

# 测试 Elicitation
> 安全删除文章 mcp-001

第十部分:Python等效实现(FastMCP)

如果你更偏好 Python,以下是使用 FastMCP 的等效实现。

步骤十:Python 全原语 Server

pip install fastmcp

创建 server.py:

from fastmcp import FastMCP, Context
from pydantic import BaseModel, Field
from typing import Optional

mcp = FastMCP("knowledge-base-full")

# ========== 数据层 ==========
articles = [
    {
        "id": "mcp-001",
        "title": "MCP 架构设计指南",
        "content": "MCP 采用客户端-服务器架构...",
        "tags": ["architecture", "guide"],
        "updated": "2026-05-20",
    },
    {
        "id": "mcp-002",
        "title": "Streamable HTTP 传输层实战",
        "content": "Streamable HTTP 是 MCP 2025 规范新增的传输方式...",
        "tags": ["transport", "http"],
        "updated": "2026-05-22",
    },
]


# ========== Tools ==========
@mcp.tool()
def search_articles(keyword: str, limit: int = 5) -> str:
    """根据关键词搜索知识库中的文章"""
    results = [
        a for a in articles
        if keyword in a["title"] or keyword in a["content"] or keyword in a["tags"]
    ]
    return str(results[:limit])


@mcp.tool()
async def create_article(
    title: str,
    content: str,
    tags: list[str] = [],
    ctx: Context = None,
) -> str:
    """创建一篇新文章到知识库"""
    new_id = f"mcp-{len(articles) + 1:03d}"

    # 发送进度通知
    await ctx.report_progress(50, 100)

    articles.append({
        "id": new_id,
        "title": title,
        "content": content,
        "tags": tags,
        "updated": "2026-05-27",
    })

    await ctx.report_progress(100, 100)
    return f"文章创建成功!ID: {new_id}"


# ========== Resources ==========
@mcp.resource("knowledge://overview")
def get_overview() -> str:
    """知识库概览"""
    return str({
        "total": len(articles),
        "tags": list(set(t for a in articles for t in a["tags"])),
    })


@mcp.resource("knowledge://articles/{article_id}")
def get_article(article_id: str) -> str:
    """获取指定文章详情"""
    article = next((a for a in articles if a["id"] == article_id), None)
    return str(article) if article else f"未找到 {article_id}"


@mcp.resource("knowledge://tags/{tag}")
def get_articles_by_tag(tag: str) -> str:
    """按标签筛选文章"""
    filtered = [a for a in articles if tag in a["tags"]]
    return str(filtered)


# ========== Prompts ==========
@mcp.prompt()
def review_article(article_id: str, focus_area: str = "all") -> str:
    """对指定文章进行专业审查"""
    article = next((a for a in articles if a["id"] == article_id), None)
    if not article:
        return f"未找到文章 {article_id}"

    return f"""请对以下文章进行专业审查,重点:{focus_area}

标题:{article['title']}
标签:{', '.join(article['tags'])}
内容:{article['content']}

请给出评分(1-10)和改进建议。"""


@mcp.prompt()
def generate_outline(topic: str, depth: str = "intermediate") -> str:
    """根据主题生成文章大纲"""
    return f"""请为以下主题生成一篇{depth}级别的技术文章大纲:

主题:{topic}
要求:三级标题结构,每节附简要说明和预计字数。"""


# ========== Sampling ==========
@mcp.tool()
async def analyze_with_ai(analysis_type: str, ctx: Context) -> str:
    """使用AI分析知识库(需要Sampling支持)"""
    summary = str([{"id": a["id"], "title": a["title"], "tags": a["tags"]} for a in articles])

    # 通过 Sampling 请求客户端 LLM
    result = await ctx.sample(
        f"分析以下知识库文章,找出{analysis_type}:\n\n{summary}",
        system_prompt="你是一位知识管理专家。",
        max_tokens=1000,
    )

    return f"## AI分析报告\n\n{result.text}"


# ========== Elicitation ==========
@mcp.tool()
async def safe_delete(article_id: str, ctx: Context) -> str:
    """安全删除文章(需要用户确认)"""
    article = next((a for a in articles if a["id"] == article_id), None)
    if not article:
        return f"未找到文章 {article_id}"

    # 通过 Elicitation 请求用户确认
    result = await ctx.elicit(
        message=f"确定要删除「{article['title']}」吗?",
        schema={
            "type": "object",
            "properties": {
                "confirmed": {"type": "boolean", "title": "确认删除"},
                "reason": {"type": "string", "title": "删除原因"},
            },
            "required": ["confirmed"],
        },
    )

    if result.get("confirmed"):
        articles.remove(article)
        return f"已删除「{article['title']}」"
    return "用户取消了删除"


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

注册到 Claude Code:

claude mcp add knowledge-base-py -- python /path/to/server.py

TypeScript 与 Python 实现对比

graph LR
    subgraph "TypeScript SDK"
        TS_T[server.tool<br/>+ zod schema]
        TS_R[server.resource<br/>+ URL模板]
        TS_P[server.prompt<br/>+ zod schema]
        TS_S[session.request<br/>sampling/createMessage]
        TS_E[session.request<br/>elicitation/create]
    end

    subgraph "Python FastMCP"
        PY_T["@mcp.tool 装饰器<br/>+ 类型注解"]
        PY_R["@mcp.resource 装饰器<br/>+ URI 模板"]
        PY_P["@mcp.prompt 装饰器<br/>+ 类型注解"]
        PY_S["ctx.sample()<br/>内置方法"]
        PY_E["ctx.elicit()<br/>内置方法"]
    end

    TS_T -.->|"等价"| PY_T
    TS_R -.->|"等价"| PY_R
    TS_P -.->|"等价"| PY_P
    TS_S -.->|"等价"| PY_S
    TS_E -.->|"等价"| PY_E
特性 TypeScript SDK Python FastMCP
Schema 定义 Zod 类型注解/Pydantic
Sampling 调用 session.request() 手动构造 ctx.sample() 内置封装
Elicitation 调用 session.request() 手动构造 ctx.elicit() 内置封装
进度通知 sendNotification() ctx.report_progress()
装饰器风格 函数式 server.tool() @mcp.tool() 装饰器
异步支持 原生 async/await 原生 async/await

第十一部分:测试策略

步骤十一:单元测试与集成测试

MCP Server 的测试分三层:工具逻辑测试、协议合规测试、端到端集成测试。

创建 src/__tests__/tools.test.ts:

import { describe, it, expect, beforeEach } from "vitest";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

describe("MCP Server 全原语测试", () => {
  let server: McpServer;
  let client: Client;

  // 单元测试:直接测试工具逻辑
  describe("Tools 逻辑", () => {
    it("search_articles 应返回匹配结果", async () => {
      // 直接调用工具内部逻辑,无需启动 Server
      const keyword = "HTTP";
      const articles = [
        { id: "001", title: "HTTP Guide", content: "...", tags: ["http"] },
        { id: "002", title: "TCP Guide", content: "...", tags: ["tcp"] },
      ];
      const results = articles.filter(
        (a) => a.title.includes(keyword) || a.tags.includes(keyword.toLowerCase())
      );
      expect(results).toHaveLength(1);
      expect(results[0].id).toBe("001");
    });
  });

  // 协议合规测试:通过 Client 连接 Server
  describe("协议合规", () => {
    it("应暴露正确的 capabilities", async () => {
      // 启动 Server 进程
      // 通过 Client 连接
      // 验证 capabilities 包含 tools, resources, prompts
    });

    it("tools/list 应返回所有注册的工具", async () => {
      // 验证工具数量和名称
    });

    it("resources/list 应返回所有资源", async () => {
      // 验证资源 URI 格式
    });

    it("resources/templates/list 应返回模板", async () => {
      // 验证模板参数定义
    });

    it("prompts/list 应返回所有提示模板", async () => {
      // 验证提示模板名称和参数
    });
  });

  // 端到端集成测试
  describe("端到端", () => {
    it("搜索 -> 读取 -> 审查 完整流程", async () => {
      // 1. 调用 search_articles Tool
      // 2. 使用返回的 ID 读取 Resource
      // 3. 使用 review-article Prompt 审查
    });
  });
});

使用 MCP Inspector 进行可视化测试:

npx @modelcontextprotocol/inspector node dist/index.js

Inspector 提供了 Web UI,可以:


第十二部分:生产部署要点

步骤十二:从 stdio 到 Streamable HTTP

开发阶段使用 stdio 传输,生产环境需要切换到 Streamable HTTP 以支持远程访问。

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

const app = express();
app.use(express.json());

const server = new McpServer({
  name: "knowledge-base-full",
  version: "1.0.0",
});

// 注册所有原语(同上)
registerTools(server);
registerResources(server);
registerPrompts(server);
registerSamplingTools(server);
registerElicitationTools(server);

// Streamable HTTP 端点
app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

app.listen(3000, () => {
  console.error("MCP Server 运行在 http://localhost:3000/mcp");
});

Claude Code 中配置远程 MCP:

{
  "mcpServers": {
    "knowledge-base-remote": {
      "type": "streamable-http",
      "url": "http://localhost:3000/mcp"
    }
  }
}

生产检查清单

graph TD
    A[部署前检查] --> B{是否需要认证?}
    B -->|是| C[配置 OAuth 2.1<br/>或 API Key]
    B -->|否| D{是否暴露公网?}
    C --> D
    D -->|是| E[必须启用认证!]
    D -->|否| F[本地/内网安全]
    E --> G[Streamable HTTP<br/>+ TLS]
    F --> G
    G --> H{Sampling/Elicitation<br/>是否必须?}
    H -->|是| I[确认客户端支持<br/>添加降级方案]
    H -->|否| J[纯 Tool 模式<br/>降低复杂度]
    I --> K[性能监控<br/>+ 错误追踪]
    J --> K
检查项 要点
错误处理 所有 Tool 必须用 isError: true 标记错误,不能抛出未捕获异常
超时控制 长 Tool 执行需发送进度通知,避免 Client 超时
能力降级 Sampling/Elicitation 不可用时提供替代方案
输入验证 Zod/Pydantic schema 是第一道防线,但业务逻辑也要二次验证
资源限制 maxTokens、文件大小、请求频率都应设上限
日志记录 所有 Tool 调用记录到 stderr(stdio 模式下 stdout 被协议占用)

完整工作流程回顾

graph TB
    subgraph "开发阶段"
        A[初始化项目] --> B[注册 Tools]
        B --> C[注册 Resources]
        C --> D[注册 Prompts]
        D --> E[实现 Sampling]
        E --> F[实现 Elicitation]
        F --> G[整合主入口]
    end

    subgraph "测试阶段"
        G --> H[单元测试<br/>工具逻辑]
        H --> I[协议合规测试<br/>capabilities]
        I --> J[MCP Inspector<br/>可视化验证]
    end

    subgraph "部署阶段"
        J --> K[stdio → Streamable HTTP]
        K --> L[认证与安全加固]
        L --> M[监控与日志]
    end

    subgraph "使用阶段"
        M --> N[claude mcp add<br/>注册到 Claude Code]
        N --> O[AI 自动调用<br/>Tool/Resource/Prompt]
        O --> P[Sampling/Elicitation<br/>增强交互]
    end

原语选择决策树

当你设计 MCP Server 时,根据需求选择合适的原语:

graph TD
    Q1{需要让AI<br/>做什么?}
    Q1 -->|执行操作| Q2{有副作用?}
    Q1 -->|读取数据| R[使用 Resource]
    Q1 -->|提供模板| P[使用 Prompt]

    Q2 -->|是| T[使用 Tool]
    Q2 -->|否,纯读取| R2{数据量大?}

    R2 -->|是| R3{需要URI寻址?}
    R2 -->|否| T2[使用 Tool<br/>也行]

    R3 -->|是| R
    R3 -->|否| T2

    T --> Q3{执行中需要<br/>AI辅助?}
    Q3 -->|是| S[使用 Sampling]
    Q3 -->|否| Q4{需要用户确认?}

    Q4 -->|是| E[使用 Elicitation]
    Q4 -->|否| DONE[完成]

    S --> DONE
    E --> DONE

延伸阅读

参考来源

  1. MCP Architecture Overview — 官方架构文档,详细说明了协议层次和原语设计
  2. Complete Guide to MCP in 2026 — 2026年MCP完整指南,涵盖架构和实现
  3. MCP Resources, Prompts, and Sampling — Tool之外三种能力的深度教程
  4. MCP Elicitation: Human-in-the-Loop — Elicitation原语的实战指南
  5. FastMCP Streaming Tutorial — FastMCP框架的流式传输和进度通知教程

训练营适用性评估:

维度 说明
适合训练营 中高级 AI 工具开发训练营、MCP 生态开发者工作坊
教学时长 约 6-8 小时(含动手实践)
学员基础 需掌握 TypeScript 或 Python、了解 MCP 基本概念
实验环境 本地 Node.js/Python 环境 + Claude Code CLI
分组建议 每 2-3 人一组,分别实现不同原语后合并

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入