如果你正在构建 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/list、resources/list、prompts/list |
能力列表 | 获取可用工具、资源、模板 |
| 实际调用 | tools/call、resources/read、prompts/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 的行为。
相关工具推荐:
- MCP Inspector — MCP Server 调试工具
- MCP SDK (TypeScript) — 官方 TypeScript SDK
- MCP SDK (Python) — 官方 Python SDK
- Smithery — MCP Server 发现与注册平台