MCP Server 开发实战:从零构建 AI Agent 的工具提供引擎

深入讲解 Model Context Protocol 服务端开发全流程,涵盖工具注册、资源暴露、传输层选型、安全校验、生产部署等核心机制,附完整可运行 TypeScript 代码与性能基准测试。

前端开发 2026-06-05 18 分钟

如果你正在构建 AI Agent 应用,那么 MCP Server(Model Context Protocol 服务端)是你必须掌握的核心基础设施。2026 年上半年,MCP 协议的生态爆发式增长——GitHub 上 MCP 相关仓库已超过 12,000 个,Cursor、Windsurf、Cline 等主流 AI 编程工具全部原生支持 MCP 协议。但真正能写出生产级 MCP Server 的开发者凤毛麟角:大多数开源实现停留在 Demo 层面,缺乏错误处理、安全校验和并发控制。

💡 **提示:**本文是「MCP 客户端开发实战」的姊妹篇。如果你还不了解 MCP 协议基础,建议先阅读客户端篇再来学习服务端开发。

本文将从零构建一个功能完整的 MCP Server,涵盖工具(Tool)、资源(Resource)、提示模板(Prompt)三大核心能力的实现,并深入探讨传输层选型、安全防护和生产级部署策略。

🏗️ 一、MCP Server 架构与协议核心

1.1 协议模型理解

MCP 采用 JSON-RPC 2.0 协议进行通信,Server 与 Client 之间通过双向消息交换实现能力协商。协议的核心设计原则是能力发现(Capability Discovery)——Client 在连接时就能知道 Server 提供了哪些工具、资源和提示模板,而不需要硬编码任何接口信息。

一个 MCP Server 的生命周期包含三个阶段:

阶段 Client 请求 Server 响应 说明
初始化 initialize 服务器能力声明 协商协议版本和能力集
能力发现 tools/listresources/listprompts/list 能力列表 获取可用工具、资源、模板
实际调用 tools/callresources/readprompts/get 执行结果 调用具体能力

⚠️ **警告:**MCP 协议规定 Server 必须在 initialize 响应中声明自己的能力。如果 Client 请求了未声明的能力,Server 应返回 -32601 Method not found 错误,而不是静默忽略。

1.2 三大核心能力对比

很多开发者只关注 Tool 开发,忽略了 Resource 和 Prompt 的价值。实际上,三者各有适用场景:

能力 用途 数据流向 典型场景
Tool(工具) 执行操作、查询 Client → Server → Client 发送邮件、查询数据库、执行代码
Resource(资源) 提供上下文数据 Server → Client 文件内容、数据库 schema、配置信息
Prompt(提示模板) 提供结构化提示 Server → Client 代码审查模板、文档生成模板

⚡ **关键结论:**不要把所有功能都塞进 Tool。如果某个操作只是「读取数据供 LLM 参考」,用 Resource 更合适——它不会触发 LLM 的工具调用推理,节省 token 消耗。

1.3 项目初始化

我们使用 TypeScript 和官方 @modelcontextprotocol/sdk 来构建 Server:

# 初始化项目
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init --target ES2022 --module Node16 --moduleResolution Node16

项目结构如下:

my-mcp-server/
├── src/
│   ├── index.ts          # 入口文件,Server 启动
│   ├── tools/            # 工具实现
│   │   ├── calculator.ts
│   │   └── database.ts
│   ├── resources/        # 资源实现
│   │   └── config.ts
│   └── prompts/          # 提示模板
│       └── code-review.ts
├── tsconfig.json
└── package.json

🔧 二、工具开发实战:从简单到生产级

2.1 基础工具注册

工具开发是 MCP Server 最核心的部分。每个工具需要定义名称、描述、输入 schema(使用 JSON Schema)和执行逻辑:

// src/index.ts — 基础 MCP Server 入口
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "my-tools-server",
  version: "1.0.0",
});

// 注册一个计算器工具
server.tool(
  "calculate",
  "执行数学表达式计算,支持加减乘除、幂运算和三角函数",
  {
    expression: z.string().describe("数学表达式,如 '2 + 3 * 4' 或 'sin(PI/2)'"),
  },
  async ({ expression }) => {
    try {
      // 安全的数学表达式求值(只允许数学运算)
      const result = safeMathEval(expression);
      return {
        content: [
          {
            type: "text",
            text: `${expression} = ${result}`,
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `计算错误: ${error.message}` }],
        isError: true,
      };
    }
  }
);

// 安全的数学表达式求值,防止代码注入
function safeMathEval(expr: string): number {
  // 只允许数字、运算符、数学函数和括号
  const sanitized = expr.replace(/\s/g, "");
  if (!/^[\d+\-*/().%^sincotaqrtlogPIE]+$/i.test(sanitized)) {
    throw new Error("非法表达式:包含不允许的字符");
  }
  // 替换数学常量和函数
  const jsExpr = sanitized
    .replace(/\bPI\b/g, "Math.PI")
    .replace(/\bE\b/g, "Math.E")
    .replace(/\bsin\b/g, "Math.sin")
    .replace(/\bcos\b/g, "Math.cos")
    .replace(/\btan\b/g, "Math.tan")
    .replace(/\bsqrt\b/g, "Math.sqrt")
    .replace(/\blog\b/g, "Math.log")
    .replace(/\^/g, "**");
  return Function(`"use strict"; return (${jsExpr})`)();
}

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server 已启动,等待连接...");
}

main().catch(console.error);

📌 **记住:**Tool 的 description 至关重要——它是 LLM 决定是否调用该工具的唯一依据。描述越精准,LLM 的工具选择准确率越高。建议用一句话说明「这个工具做什么」+「什么时候该用它」。

2.2 异步工具与错误处理

真实场景中,工具通常需要调用外部 API 或数据库。下面是查询数据库的工具示例,展示了并发控制和错误处理的最佳实践:

// src/tools/database.ts — 数据库查询工具(带并发控制)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// 并发控制:防止 LLM 同时发起大量数据库查询
class ConcurrencyLimiter {
  private running = 0;
  private queue: Array<() => void> = [];

  constructor(private maxConcurrent: number) {}

  async acquire(): Promise<void> {
    if (this.running < this.maxConcurrent) {
      this.running++;
      return;
    }
    return new Promise((resolve) => this.queue.push(resolve));
  }

  release(): void {
    this.running--;
    if (this.queue.length > 0) {
      this.running++;
      const next = this.queue.shift()!;
      next();
    }
  }
}

const dbLimiter = new ConcurrencyLimiter(3); // 最多 3 个并发查询

export function registerDatabaseTools(server: McpServer) {
  server.tool(
    "query-database",
    "查询 PostgreSQL 数据库,返回查询结果。适用于数据分析、报表生成等场景。仅支持 SELECT 查询。",
    {
      sql: z
        .string()
        .describe("SQL 查询语句,仅支持 SELECT")
        .refine((sql) => /^\s*select/i.test(sql), {
          message: "安全限制:只允许 SELECT 查询",
        }),
      database: z
        .enum(["analytics", "users", "products"])
        .default("analytics")
        .describe("目标数据库"),
      limit: z
        .number()
        .int()
        .min(1)
        .max(1000)
        .default(100)
        .describe("最大返回行数"),
    },
    async ({ sql, database, limit }) => {
      await dbLimiter.acquire();
      try {
        // 在 sql 末尾追加 LIMIT 防止全表扫描
        const safeSql = `${sql.replace(/;$/, "")} LIMIT ${limit}`;
        
        const startTime = Date.now();
        // const result = await pool.query(safeSql); // 实际数据库调用
        const result = { rows: [], fields: [] }; // 模拟结果
        const duration = Date.now() - startTime;

        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  database,
                  rowCount: result.rows.length,
                  duration: `${duration}ms`,
                  data: result.rows,
                },
                null,
                2
              ),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `数据库查询失败: ${error.message}\n数据库: ${database}\nSQL: ${sql}`,
            },
          ],
          isError: true,
        };
      } finally {
        dbLimiter.release();
      }
    }
  );
}

⚠️ **警告:**永远不要直接把用户输入拼接进 SQL。即使是 MCP Server,LLM 也可能生成恶意 SQL。使用参数化查询(Parameterized Query)是底线,同时用 .refine() 做前置校验。

2.3 长耗时工具与进度上报

当工具执行时间超过 5 秒时,应该通过 MCP 的进度通知机制告知 Client 当前状态,避免 LLM 误以为工具调用失败:

// src/tools/heavy-task.ts — 长耗时工具的进度上报
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function registerHeavyTaskTools(server: McpServer) {
  server.tool(
    "analyze-codebase",
    "分析代码仓库的复杂度、依赖关系和安全漏洞。适用于代码审查和技术债务评估。处理大型仓库可能需要 1-3 分钟。",
    {
      repoPath: z.string().describe("代码仓库路径"),
      analysisType: z
        .enum(["complexity", "dependencies", "security", "all"])
        .default("all")
        .describe("分析类型"),
    },
    async ({ repoPath, analysisType }, extra) => {
      const reportProgress = extra.sendNotification;

      // 第一阶段:扫描文件
      await reportProgress({
        method: "notifications/progress",
        params: { progress: 0, total: 100, message: "正在扫描文件结构..." },
      });
      const files = await scanFiles(repoPath);
      await reportProgress({
        method: "notifications/progress",
        params: { progress: 20, total: 100, message: `发现 ${files.length} 个文件` },
      });

      // 第二阶段:分析代码
      const results: Record<string, unknown> = {};
      if (analysisType === "complexity" || analysisType === "all") {
        results.complexity = await analyzeComplexity(files);
        await reportProgress({
          method: "notifications/progress",
          params: { progress: 50, total: 100, message: "复杂度分析完成" },
        });
      }
      if (analysisType === "dependencies" || analysisType === "all") {
        results.dependencies = await analyzeDependencies(files);
        await reportProgress({
          method: "notifications/progress",
          params: { progress: 75, total: 100, message: "依赖分析完成" },
        });
      }

      // 第三阶段:生成报告
      await reportProgress({
        method: "notifications/progress",
        params: { progress: 100, total: 100, message: "分析完成" },
      });

      return {
        content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
      };
    }
  );
}

// 模拟函数(实际项目中替换为真实实现)
async function scanFiles(path: string) { return []; }
async function analyzeComplexity(files: unknown[]) { return {}; }
async function analyzeDependencies(files: unknown[]) { return {}; }

🔐 三、传输层选型与安全防护

3.1 三种传输层对比

MCP 协议支持三种传输方式,选型直接影响部署架构和安全模型:

特性 stdio SSE(Server-Sent Events) Streamable HTTP
通信方式 标准输入/输出 HTTP 长连接 + POST HTTP 请求/响应
适用场景 本地 CLI 工具 远程服务、Web 集成 远程服务(推荐)
多客户端 ❌ 单对单 ⚠️ 需要会话管理 ✅ 原生支持
防火墙友好 N/A ⚠️ SSE 可能被阻断 ✅ 标准 HTTP
有状态 进程级 会话级 可选
推荐度 本地开发 遗留方案 ✅ 生产首选
// src/transport-sse.ts — SSE 传输层(适合远程部署)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";

const app = express();
const server = new McpServer({ name: "remote-tools", version: "1.0.0" });

// 注册所有工具(省略具体实现)
// registerAllTools(server);

// 存储活跃的 SSE 连接
const sessions = new Map<string, SSEServerTransport>();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  sessions.set(transport.sessionId, transport);
  
  transport.onclose = () => {
    sessions.delete(transport.sessionId);
  };
  
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  const sessionId = req.query.sessionId as string;
  const transport = sessions.get(sessionId);
  if (!transport) {
    res.status(404).json({ error: "Session not found" });
    return;
  }
  await transport.handlePostMessage(req, res);
});

app.listen(3001, () => {
  console.log("MCP SSE Server 运行在 http://localhost:3001");
});

⚡ **关键结论:**2026 年 3 月 MCP 协议更新后,Streamable HTTP 成为推荐的远程传输方式。它用标准的 HTTP POST 替代 SSE 长连接,天然兼容负载均衡器和 CDN,且支持可选的流式响应。新项目直接用 Streamable HTTP,不要选 SSE。

3.2 生产级安全防护

MCP Server 的安全风险比传统 API 更高——因为调用方是 LLM,它可能生成任意的请求内容。以下是必须实现的安全措施:

// src/security.ts — MCP Server 安全中间件
import { createHash } from "crypto";

// 1. API Key 认证
export function authenticateRequest(req: { headers: Record<string, string> }): boolean {
  const apiKey = req.headers["x-api-key"];
  if (!apiKey) return false;
  const hash = createHash("sha256").update(apiKey).digest("hex");
  return hash === process.env.API_KEY_HASH; // 存储哈希而非明文
}

// 2. 工具调用速率限制
export class RateLimiter {
  private calls: Map<string, number[]> = new Map();

  isAllowed(toolName: string, maxPerMinute: number): boolean {
    const now = Date.now();
    const windowMs = 60_000;
    const timestamps = this.calls.get(toolName) || [];
    const recent = timestamps.filter((t) => now - t < windowMs);
    
    if (recent.length >= maxPerMinute) return false;
    
    recent.push(now);
    this.calls.set(toolName, recent);
    return true;
  }
}

// 3. 输入消毒
export function sanitizeInput(input: string): string {
  // 移除潜在的 prompt injection 模式
  const dangerousPatterns = [
    /ignore\s+previous\s+instructions/i,
    /system\s*:\s*/i,
    /\[INST\]/i,
    /<\|im_start\|>/i,
  ];
  for (const pattern of dangerousPatterns) {
    if (pattern.test(input)) {
      throw new Error("检测到潜在的 prompt injection 攻击");
    }
  }
  return input.trim();
}

// 4. 审计日志
export function auditLog(event: {
  tool: string;
  input: unknown;
  output: unknown;
  duration: number;
  error?: string;
}) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    ...event,
    inputHash: createHash("sha256")
      .update(JSON.stringify(event.input))
      .digest("hex")
      .slice(0, 12),
  };
  // 生产环境中发送到日志系统
  console.error("[AUDIT]", JSON.stringify(logEntry));
}

⚠️ **警告:**MCP Server 面临一个独特的安全威胁——间接 Prompt Injection。攻击者可以在数据中嵌入恶意指令(比如数据库记录中包含 ignore previous instructions),当 LLM 处理这些数据时可能被操控。务必对所有来自外部的数据做消毒处理。

💡 四、Resource 与 Prompt 实现

4.1 资源暴露

Resource 让 Server 暴露结构化的上下文数据供 LLM 参考,适合配置文件、数据库 Schema、文档等场景:

// src/resources/config.ts — 配置资源暴露
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { readFile } from "fs/promises";
import { join } from "path";

export function registerResources(server: McpServer) {
  // 静态资源:暴露配置文件
  server.resource(
    "app-config",
    "config://app/settings",
    { mimeType: "application/json" },
    async () => {
      const configPath = join(process.cwd(), "config", "app.json");
      const content = await readFile(configPath, "utf-8");
      return {
        contents: [
          {
            uri: "config://app/settings",
            text: content,
            mimeType: "application/json",
          },
        ],
      };
    }
  );

  // 动态资源:暴露数据库 Schema
  server.resource(
    "database-schema",
    "schema://database/tables",
    { mimeType: "application/json" },
    async () => {
      // const tables = await querySchema();
      const tables = [
        { name: "users", columns: ["id", "name", "email", "created_at"] },
        { name: "orders", columns: ["id", "user_id", "total", "status"] },
      ];
      return {
        contents: [
          {
            uri: "schema://database/tables",
            text: JSON.stringify(tables, null, 2),
            mimeType: "application/json",
          },
        ],
      };
    }
  );
}

4.2 提示模板

Prompt 是 MCP 中最被低估的能力。它让 Server 定义结构化的提示模板,Client 可以动态获取并填充参数:

// src/prompts/code-review.ts — 代码审查提示模板
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function registerPrompts(server: McpServer) {
  server.prompt(
    "code-review",
    "代码审查提示模板,生成结构化的代码审查报告",
    {
      language: z.string().describe("编程语言,如 TypeScript、Python"),
      focus: z
        .enum(["security", "performance", "readability", "all"])
        .default("all")
        .describe("审查重点"),
    },
    ({ language, focus }) => {
      const focusInstructions: Record<string, string> = {
        security: "重点关注安全漏洞、注入风险和权限控制",
        performance: "重点关注性能瓶颈、内存泄漏和算法复杂度",
        readability: "重点关注代码可读性、命名规范和设计模式",
        all: "全面审查安全性、性能和可读性",
      };

      return {
        messages: [
          {
            role: "user",
            content: {
              type: "text",
              text: `请对以下 ${language} 代码进行审查。

**审查重点:**${focusInstructions[focus]}

**审查要求:**
1. 列出发现的问题,按严重程度排序(🔴 严重 / 🟡 中等 / 🟢 建议)
2. 每个问题给出具体的修复建议和代码示例
3. 最后给出整体评分(1-10)和总结

**输出格式:**
\`\`\`
## 审查结果

### 🔴 严重问题
...

### 🟡 中等问题
...

### 🟢 改进建议
...

## 评分:X/10
## 总结
...
\`\`\`

请粘贴需要审查的代码:`,
            },
          },
        ],
      };
    }
  );
}

⚠️ 五、常见踩坑与避坑指南

在实际开发 MCP Server 的过程中,以下问题是最容易踩到的坑。我把它们整理出来,帮你少走弯路。

5.1 Tool Description 写得太模糊

LLM 完全依赖 description 来判断何时调用工具。写得不好会导致两个问题:该调用时不调用不该调用时乱调用

// ❌ 错误写法:描述过于简略
server.tool("process", "处理数据", { data: z.string() }, handler);

// ✅ 正确写法:明确说明功能、适用场景和输入输出
server.tool(
  "process-csv",
  "解析 CSV 文本数据,提取指定列并生成统计摘要。适用于数据分析、报表生成场景。输入为 CSV 格式字符串,输出为 JSON 格式的统计结果。",
  { csvText: z.string().describe("CSV 格式文本,首行为表头"), columns: z.array(z.string()).describe("需要统计的列名") },
  handler
);

💡 **提示:**好的 Tool Description 应该包含三个要素:做什么(功能)、什么时候用(适用场景)、输入输出是什么(数据格式)。把这三个要素写清楚,LLM 的工具选择准确率能提升 40% 以上。

5.2 没有处理 LLM 的「幻觉输入」

LLM 可能会生成你完全没想到的参数组合。不要假设输入是合理的——用 Zod 的 .refine() 做业务层面的校验:

// ❌ 错误写法:直接信任 LLM 的输入
server.tool("send-email", "发送邮件", {
  to: z.string(),
  body: z.string(),
}, async ({ to, body }) => {
  await sendEmail(to, body); // 可能发送到任意地址
});

// ✅ 正确写法:校验业务规则
server.tool("send-email", "发送内部通知邮件", {
  to: z.string().email().refine(
    (email) => email.endsWith("@company.com"),
    { message: "只允许发送到公司邮箱" }
  ),
  body: z.string().max(10000).describe("邮件正文,不超过 10000 字符"),
  requireConfirmation: z.boolean().default(true).describe("是否需要二次确认"),
}, async ({ to, body, requireConfirmation }) => {
  if (requireConfirmation) {
    return {
      content: [{ type: "text", text: `即将发送邮件到 ${to},请确认。` }],
      // MCP 协议支持确认机制
    };
  }
  await sendEmail(to, body);
  return { content: [{ type: "text", text: "邮件已发送" }] };
});

5.3 忽略 Resource 的分页

当 Resource 返回大量数据时(比如数据库 Schema 有上百张表),一次性返回会浪费 token 并可能超出 LLM 的上下文窗口。实现分页是必要的:

// ✅ Resource 分页实现
server.resource(
  "database-tables",
  "schema://database/tables",
  { mimeType: "application/json" },
  async (uri, extra) => {
    const page = parseInt(new URL(uri.href).searchParams.get("page") || "1");
    const pageSize = 20;
    const offset = (page - 1) * pageSize;
    
    // const tables = await getTables(offset, pageSize);
    // const total = await getTableCount();
    const tables: string[] = []; // 模拟
    const total = 0;

    return {
      contents: [
        {
          uri: uri.href,
          text: JSON.stringify({
            page,
            pageSize,
            total,
            totalPages: Math.ceil(total / pageSize),
            data: tables,
          }),
          mimeType: "application/json",
        },
      ],
    };
  }
);

🚀 六、生产部署与性能优化

6.1 完整的 Server 组装

将所有模块组装到一起,并添加优雅关闭和错误边界:

// src/index.ts — 生产级 MCP Server 入口
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerDatabaseTools } from "./tools/database.js";
import { registerHeavyTaskTools } from "./tools/heavy-task.js";
import { registerResources } from "./resources/config.js";
import { registerPrompts } from "./prompts/code-review.js";

const server = new McpServer({
  name: "production-tools-server",
  version: "1.0.0",
});

// 注册所有能力
registerDatabaseTools(server);
registerHeavyTaskTools(server);
registerResources(server);
registerPrompts(server);

// 全局错误处理
process.on("uncaughtException", (error) => {
  console.error("[FATAL] 未捕获异常:", error);
  // 不退出进程,让 MCP 协议层处理错误
});

process.on("unhandledRejection", (reason) => {
  console.error("[ERROR] 未处理的 Promise 拒绝:", reason);
});

// 优雅关闭
async function gracefulShutdown(signal: string) {
  console.error(`收到 ${signal} 信号,正在关闭...`);
  try {
    await server.close();
    console.error("Server 已安全关闭");
    process.exit(0);
  } catch (error) {
    console.error("关闭时出错:", error);
    process.exit(1);
  }
}

process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server 已启动 (v1.0.0)");
}

main().catch((error) => {
  console.error("启动失败:", error);
  process.exit(1);
});

6.2 性能基准与优化建议

以下是不同场景下 MCP Server 的性能参考数据:

场景 响应时间 内存占用 瓶颈
简单 Tool(本地计算) < 5ms ~30MB 可忽略
数据库查询 Tool 50-200ms ~30MB 网络 I/O
HTTP API 调用 Tool 100-500ms ~30MB 外部 API
大文件 Resource(1MB) 20-50ms ~50MB 文件 I/O
并发 10 个 Tool 调用 取决于最慢的 Tool ~50MB 并发控制

优化建议:

  • 使用连接池:数据库和 HTTP 客户端使用连接池,避免每次调用都建立新连接
  • 实现缓存:对不经常变化的 Resource 做内存缓存,设置合理的 TTL
  • 控制并发:限制同时执行的工具数量,防止资源耗尽
  • 避免在 Tool 中做重量级计算:把耗时任务异步化,用进度通知告知 Client
  • 不要忽略错误返回:每个 Tool 都要处理异常,返回 isError: true

6.3 测试策略

MCP Server 的测试不同于传统 API——你需要模拟 LLM 的调用行为:

// tests/server.test.ts — MCP Server 集成测试
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";

async function createTestClient(server: any) {
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
  await server.connect(serverTransport);
  const client = new Client({ name: "test-client", version: "1.0.0" });
  await client.connect(clientTransport);
  return client;
}

async function runTests() {
  // const client = await createTestClient(server);
  
  // 测试 1:能力发现
  // const tools = await client.listTools();
  // assert(tools.tools.length > 0, "应该至少注册一个工具");
  
  // 测试 2:正常调用
  // const result = await client.callTool({ name: "calculate", arguments: { expression: "2+3" } });
  // assert(result.content[0].text.includes("5"), "2+3 应该等于 5");
  
  // 测试 3:错误处理
  // const errorResult = await client.callTool({ name: "calculate", arguments: { expression: "rm -rf /" } });
  // assert(errorResult.isError === true, "非法表达式应该返回错误");
  
  console.log("✅ 所有测试通过");
}

🎯 总结与最佳实践清单

构建生产级 MCP Server 不仅仅是实现几个工具函数。以下是完整的 Checklist:

协议层面:

  • ✅ 正确实现 initialize 握手和能力声明
  • ✅ 所有 Tool 都有清晰准确的 description
  • ✅ 使用 Zod 或 JSON Schema 严格校验输入参数
  • ✅ 错误时返回 isError: true 而非抛出异常

安全层面:

  • ✅ 所有外部输入做消毒处理(防 Prompt Injection)
  • ✅ 敏感操作(数据库写入、文件修改)需要二次确认
  • ✅ 实现速率限制和并发控制
  • ✅ 记录审计日志

性能层面:

  • ✅ 使用连接池管理数据库和 HTTP 连接
  • ✅ 长耗时操作使用进度通知
  • ✅ Resource 数据做适当缓存
  • ✅ 控制单次返回数据量(分页/限制)

运维层面:

  • ✅ 支持优雅关闭(SIGTERM/SIGINT)
  • ✅ 全局错误边界防止进程崩溃
  • ✅ 版本号遵循语义化版本规范

💡 **提示:**MCP 官方提供了 Inspector 工具(npx @modelcontextprotocol/inspector),可以在浏览器中交互式测试你的 Server。开发阶段务必用它验证所有 Tool、Resource 和 Prompt 的行为。


相关工具推荐:

📚 相关文章