Model Context Protocol (MCP) 详解:构建标准化AI插件服务器的实践指南
在实际 AI 应用开发中,一个长期存在的痛点是如何让不同的 AI 模型、工具和数据源高效、安全地协同工作。开发者常常需要为每个项目定制复杂的集成逻辑,编写大量的胶水代码,这不仅耗时费力,也使得系统难以维护和扩展。为了解决这一普遍性问题,OpenAI 联合 Anthropic、Google、Microsoft 等多家公司,共同推出了一个名为 Model Context Protocol (MCP) 的开放标准。这个标准旨在为 AI 应用中的“智能体”或“助手”定义一套统一的插件接口,让它们能够像人类使用鼠标和键盘操作电脑一样,安全、可控地调用外部工具、访问数据和执行操作。
对于正在构建或集成 AI 能力的开发者而言,理解 MCP 至关重要。它并非一个具体的 SDK 或框架,而是一套协议规范,类似于 HTTP 之于 Web 应用。掌握 MCP,意味着你能让自己的 AI 应用或工具更容易地被 Claude、ChatGPT 等主流 AI 平台集成,也能让你更便捷地利用外部生态的能力。本文将深入解析 MCP 的核心概念、工作原理,并通过一个从零构建 MCP 服务器的完整示例,带你理解如何将这套标准落地到实际项目中。无论你是 AI 应用开发者、工具提供方,还是希望提升 AI 助手能力的用户,都能从中获得清晰的实践路径。
1. 理解 MCP:为什么需要一个新的“插件”标准?
在深入技术细节之前,我们必须先厘清 MCP 要解决的根本问题。现有的 AI 工具集成方式,如 OpenAI 的 Function Calling、LangChain Tools 或自定义 API,虽然功能强大,但存在几个关键挑战:
- 碎片化与重复劳动 :每个 AI 平台(如 ChatGPT, Claude Desktop, Cursor)都可能定义自己的一套插件或工具接入方式。开发者若想支持多个平台,就需要为每个平台重复开发适配逻辑。
- 安全与权限控制粒度粗 :传统方式下,AI 助手一旦获得某个工具的调用权限,往往就能执行该工具的所有操作,缺乏对敏感操作(如删除数据、调用付费 API)的细粒度控制。
- 上下文管理复杂 :AI 模型有上下文长度限制。如何高效地将大型文件(如代码库、长文档)的内容作为上下文提供给模型,同时避免浪费宝贵的 Token,是一个工程难题。
- 开发体验不统一 :工具的开发、测试、部署流程缺乏标准,导致生态工具质量参差不齐,开发者学习和调试成本高。
MCP 正是为了应对这些挑战而设计的。它的核心思想是 “服务器-客户端”架构 和 “资源-工具”模型 。
1.1 MCP 的核心架构:服务器与客户端
你可以把 MCP 想象成 AI 世界的“驱动程序”标准。
- MCP 服务器 :扮演“驱动程序”或“适配器”的角色。它封装了对某个特定数据源或工具的所有访问逻辑。例如,一个“GitHub 服务器”知道如何与 GitHub API 通信,获取仓库列表、读取文件;一个“数据库服务器”知道如何连接数据库并执行查询。
- MCP 客户端 :通常是 AI 应用本身,如 Claude Desktop、Cursor 或你自己开发的 AI 助手。客户端负责启动、管理服务器,并向 AI 模型(如 GPT-4, Claude 3)提供服务器所暴露的能力。
客户端与服务器之间通过 标准化的 JSON-RPC 协议 进行通信。这意味着只要你的工具按照 MCP 协议实现了服务器,它就能被任何兼容 MCP 的客户端使用,无需为每个客户端重写一遍。
1.2 资源与工具:MCP 的能力抽象
MCP 将服务器能提供的能力抽象为两种基本类型:
- 资源 :代表静态或动态的数据内容,可以被 AI 模型“读取”以获取上下文。例如,一个文件、数据库查询结果、API 接口文档、今天的天气数据。资源有唯一的标识符(URI)和可选的元数据(如 MIME 类型)。
- 工具 :代表可执行的操作,可以被 AI 模型“调用”以改变状态或执行任务。例如,“执行一个 SQL 查询”、“发送一封邮件”、“创建一个 GitHub Issue”。工具包含名称、描述、输入参数定义。
这种分离带来了巨大优势:
- 安全 :客户端可以只向模型暴露“读取文件”的资源,而不暴露“删除文件”的工具。
- 高效 :客户端可以智能地按需加载资源内容到模型上下文,而不是一股脑塞进去。
- 可发现性 :模型可以动态查询服务器提供了哪些资源和工具,实现“即插即用”。
2. 环境准备与开发工具选择
在开始构建 MCP 服务器之前,我们需要搭建开发环境。MCP 协议本身是语言无关的,官方提供了 Python、TypeScript/JavaScript 和 Swift 的 SDK 来简化开发。这里我们选择 TypeScript/Node.js 环境进行演示,因为它在前端和后端开发中都非常流行,且 MCP 的 TS SDK 成熟度较高。
2.1 基础环境要求
请确保你的系统已安装以下软件:
- Node.js : 版本 18 或更高。推荐使用 LTS 版本。
- npm 或 yarn 或 pnpm : 包管理工具。
- 一个代码编辑器 :如 VS Code,并安装 TypeScript 支持。
你可以通过以下命令检查环境:
node --version
npm --version
2.2 初始化项目与安装依赖
首先,创建一个新的目录并初始化一个 Node.js 项目。
mkdir mcp-example-server
cd mcp-example-server
npm init -y
接下来,安装 MCP TypeScript SDK 和必要的类型定义。我们还需要 zod 库,因为 SDK 使用它进行参数验证。
npm install @modelcontextprotocol/sdk zod
npm install --save-dev typescript @types/node tsx
@modelcontextprotocol/sdk: 官方提供的 MCP SDK,包含了建立连接、定义资源工具、处理请求的所有核心功能。zod: 一个功能强大的 TypeScript 模式声明和验证库。typescript,@types/node: TypeScript 编译器和 Node.js 类型定义。tsx: 一个 TypeScript 执行器,可以让我们直接运行.ts文件,无需手动编译。
现在,创建 tsconfig.json 文件来配置 TypeScript 编译器:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
创建项目基础结构:
mkdir src
touch src/index.ts
至此,项目骨架已经搭建完成。你的 package.json 应该类似于:
{
"name": "mcp-example-server",
"version": "1.0.0",
"description": "A simple MCP server example",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "tsx src/index.ts",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.24",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}
3. 构建你的第一个 MCP 服务器:一个系统信息查询器
为了直观理解 MCP 服务器如何工作,我们将构建一个简单的服务器,它提供两个能力:
- 一个资源 :以文本形式提供当前系统的基本信息(如平台、内存、Node 版本)。
- 一个工具 :执行一个简单的命令(如
echo)并返回结果。
3.1 创建服务器实例与定义清单
在 src/index.ts 中,我们首先导入 SDK,创建一个服务器实例,并定义服务器的“清单”。清单相当于服务器的自我介绍,告诉客户端它叫什么、提供什么能力。
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// 1. 创建 Server 实例
const server = new Server(
{
name: 'system-info-server', // 服务器名称
version: '1.0.0', // 版本
},
{
capabilities: { // 声明服务器支持的能力
resources: {}, // 支持资源相关操作
tools: {}, // 支持工具相关操作
},
}
);
3.2 实现“列出资源”和“读取资源”的处理逻辑
接下来,我们实现第一个功能:提供一个名为 system://info 的资源,当客户端请求读取它时,返回系统信息。
// 2. 处理 `resources/list` 请求:告诉客户端我们有哪些资源
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: 'system://info', // 资源的唯一标识符
name: 'System Information', // 对人类友好的名称
description: 'Basic information about the current system',
mimeType: 'text/plain', // 资源内容的类型
},
],
};
});
// 3. 处理 `resources/read` 请求:当客户端需要资源内容时,返回具体数据
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'system://info') {
// 动态生成系统信息
const systemInfo = `
Platform: ${process.platform}
Architecture: ${process.arch}
Node.js Version: ${process.version}
Memory Usage: ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)} MB
Uptime: ${Math.floor(process.uptime())} seconds
`.trim();
return {
contents: [
{
uri: uri,
mimeType: 'text/plain',
text: systemInfo,
},
],
};
}
// 如果请求的 URI 不是我们提供的,则抛出错误
throw new Error(`Resource not found: ${uri}`);
});
关键点解释 :
uri: 是资源的全局标识。我们使用了自定义的system://协议头,你也可以使用file://,https://等。mimeType: 告诉客户端如何解析内容。text/plain表示纯文本,也可以是application/json,text/markdown等。- 资源的内容是动态生成的,每次读取都可能不同,这展示了 MCP 资源可以是“动态”的。
3.3 实现“列出工具”和“调用工具”的处理逻辑
现在,我们实现第二个功能:提供一个名为 execute_command 的工具,它可以接收一个命令参数并执行。
// 4. 导入 `zod` 来定义工具参数的严格模式
import { z } from 'zod';
// 5. 处理 `tools/list` 请求:告诉客户端我们有哪些工具
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'execute_command', // 工具名称,AI 模型将用这个名字来调用
description: 'Execute a simple shell command and return its output.',
inputSchema: { // 定义工具需要的输入参数
type: 'object',
properties: {
command: {
type: 'string',
description: 'The shell command to execute (e.g., echo Hello)',
},
},
required: ['command'],
},
},
],
};
});
// 6. 处理 `tools/call` 请求:执行工具并返回结果
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'execute_command') {
// 使用 zod 验证输入参数
const schema = z.object({
command: z.string().min(1).max(100), // 限制命令长度,简单安全措施
});
const parsedArgs = schema.parse(args);
const { command } = parsedArgs;
// 警告:在实际生产中,直接执行用户输入的命令极其危险!
// 这里仅为演示,且做了简单限制。生产环境必须使用白名单或沙箱。
if (command.toLowerCase().includes('rm') || command.includes('&&')) {
return {
content: [
{
type: 'text',
text: `Error: Command '${command}' is not allowed for security reasons.`,
},
],
isError: true,
};
}
// 使用 Node.js 的 child_process 执行命令
const { exec } = await import('child_process');
const { promisify } = await import('util');
const execAsync = promisify(exec);
try {
const { stdout, stderr } = await execAsync(command, { timeout: 5000 });
const output = stderr ? `STDERR: ${stderr}\nSTDOUT: ${stdout}` : stdout;
return {
content: [
{
type: 'text',
text: `Command executed successfully.\nOutput:\n${output}`,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error executing command: ${error.message}`,
},
],
isError: true,
};
}
}
throw new Error(`Unknown tool: ${name}`);
});
关键点解释与安全警告 :
inputSchema: 使用 JSON Schema 定义了工具的参数。这至关重要,因为 AI 客户端(如 Claude)会利用这个模式来理解如何调用工具。- 安全是重中之重 :上述
execute_command工具是一个 极不安全的示例 ,仅用于演示 MCP 工具调用的流程。在实际项目中,绝对不要提供能直接执行任意 Shell 命令的工具。应该提供具体的、受控的工具,如search_files,query_database。 - 我们使用了
zod进行运行时参数验证,并添加了简单的命令黑名单,但这远远不够。生产环境的工具必须进行严格的输入校验、权限控制和操作审计。
3.4 启动服务器并建立传输连接
MCP 服务器通过 标准输入输出 与客户端通信,这是一种简单且通用的进程间通信方式。
// 7. 启动服务器:连接到 stdio 传输层
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP System Info Server running on stdio...');
}
main().catch((error) => {
console.error('Server fatal error:', error);
process.exit(1);
});
console.error 用于输出日志,因为标准输出被用于 JSON-RPC 协议通信。服务器启动后,就会等待客户端通过 stdin 发送请求,并通过 stdout 返回响应。
至此,一个完整的 MCP 服务器代码已经完成。你可以通过 npm run dev 启动它,但它目前还无法独立工作,需要一个 MCP 客户端来驱动。
4. 运行验证:使用 Claude Desktop 测试你的 MCP 服务器
为了测试我们构建的服务器,需要一个兼容 MCP 的客户端。Anthropic 的 Claude Desktop 应用原生支持 MCP,是当前最方便的测试工具。
4.1 配置 Claude Desktop 加载自定义服务器
-
找到 Claude Desktop 配置目录 :
- macOS :
~/Library/Application Support/Claude/claude_desktop_config.json - Windows :
%APPDATA%\Claude\claude_desktop_config.json - Linux :
~/.config/Claude/claude_desktop_config.json
- macOS :
-
编辑配置文件 :如果文件不存在,就创建它。添加以下配置,指向你刚刚编写的服务器入口文件。
{
"mcpServers": {
"system-info": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/mcp-example-server/src/index.ts"
],
"env": {
"NODE_ENV": "development"
}
}
}
}
注意 :你必须将 /ABSOLUTE/PATH/TO/YOUR/ 替换为你项目 index.ts 文件的 绝对路径 。由于我们使用 tsx 直接运行 TypeScript, command 是 node , args 里通过 tsx 来执行文件。确保 tsx 已在全局安装 ( npm install -g tsx ) 或在项目 node_modules/.bin 路径下。
更稳妥的做法是,先在 package.json 中定义一个启动脚本,然后配置调用 npm run start 。但为了简化,我们直接使用 tsx 。
- 重启 Claude Desktop :保存配置文件后,完全退出并重新启动 Claude Desktop 应用。
4.2 在 Claude 中验证功能
重启后,在 Claude Desktop 中新建一个对话。如果配置成功,Claude 会在后台启动你的 MCP 服务器。
-
测试资源读取 :尝试让 Claude “查看系统信息”。Claude 会自动发现
system://info资源,并将其内容作为上下文读取。你应该能看到 Claude 回复中包含了你服务器生成的平台、Node 版本等信息。- 用户输入 : “告诉我当前系统的一些信息。”
- Claude 行为 : Claude 识别到需要
system://info资源,通过 MCP 协议向你的服务器发起resources/read请求,获取内容后,整合到回复中。
-
测试工具调用 :尝试让 Claude “执行 echo 命令”。
- 用户输入 : “请帮我执行命令
echo Hello MCP。” - Claude 行为 : Claude 识别到需要调用
execute_command工具,并自动构造参数{“command”: “echo Hello MCP”},通过 MCP 协议发起tools/call请求。你的服务器处理请求,执行命令,并将结果“Hello MCP”返回给 Claude,Claude 再呈现给你。 - 测试安全限制 :尝试输入 “执行
rm -rf /” 或 “执行ls && cat /etc/passwd”。根据我们的代码,Claude 应该会返回工具调用出错的提示,说明命令不被允许。
- 用户输入 : “请帮我执行命令
验证成功的关键标志 :
- Claude 的回复中包含了来自你服务器的动态数据。
- Claude 能够理解并调用你定义的工具。
- 在 Claude Desktop 的日志或你服务器的控制台(如果你从终端启动)中,可以看到 JSON-RPC 请求和响应的日志(如果 SDK 开启了调试)。
5. 深入 MCP:协议细节、错误处理与生产化考量
通过上面的示例,我们跑通了 MCP 的核心流程。但要将其用于实际项目,还需要理解更多细节。
5.1 MCP 协议通信流程
一次完整的交互通常如下:
- 连接 :客户端通过 stdio 或 SSE 启动服务器进程,建立连接。
- 初始化 :客户端与服务器交换
initialize和initialized通知,协商协议版本。 - 能力发现 :客户端发送
tools/list和resources/list请求,获取服务器能力清单。 - 交互 :
- 资源拉取 :当 AI 模型需要某资源作为上下文时,客户端发送
resources/read。 - 工具调用 :当 AI 模型决定调用工具时,客户端发送
tools/call。
- 资源拉取 :当 AI 模型需要某资源作为上下文时,客户端发送
- 通知 :服务器也可以主动向客户端发送
notifications,例如通知资源内容已更新。
5.2 增强服务器的健壮性
我们的示例服务器缺少错误处理和日志,在生产中这是不可接受的。
// 改进:添加全局错误处理与请求日志
server.onerror = (error) => {
console.error('[MCP Server Error]', error);
};
// 在 setRequestHandler 内部,进行更细致的错误捕获
server.setRequestHandler(CallToolRequestSchema, async (request) => {
console.log(`[Request] tools/call: ${JSON.stringify(request)}`);
try {
// ... 原有的处理逻辑 ...
console.log(`[Success] tools/call: ${name}`);
return result;
} catch (error: any) {
console.error(`[Error] tools/call ${name}:`, error);
return {
content: [{
type: 'text',
text: `Internal server error: ${error.message}`,
}],
isError: true,
};
}
});
5.3 生产环境部署与安全清单
将 MCP 服务器用于生产环境,必须考虑以下方面:
| 考量维度 | 具体措施与建议 |
|---|---|
| 安全性 | 1. 输入验证 :对所有工具参数使用如 zod 的库进行严格校验。 2. 权限最小化 :每个工具只赋予完成其功能所需的最小权限。避免 execute_command 这类高危工具。 3. 认证与授权 :如果服务器访问敏感数据(数据库、内部 API),必须实现认证。可以在服务器启动时读取环境变量中的令牌,或在每个请求中验证。 4. 审计日志 :记录所有工具调用的时间、参数、执行者和结果。 |
| 性能与可靠性 | 1. 资源缓存 :对于不常变的资源,实现缓存机制,避免重复计算或查询。 2. 超时控制 :为所有外部调用(API、数据库)设置合理的超时时间。 3. 连接池 :对于数据库类服务器,使用连接池管理连接。 4. 健康检查 :实现一个简单的健康检查端点或信号。 |
| 配置化 | 1. 环境变量 :所有配置(如 API 密钥、数据库连接串、白名单)都应通过环境变量传入,而非硬编码。 2. 配置文件 :复杂配置可使用 JSON 或 YAML 文件,并通过环境变量指定路径。 |
| 可观测性 | 1. 结构化日志 :使用 pino , winston 等库输出 JSON 格式日志,便于收集和分析。 2. 指标监控 :暴露关键指标(请求量、耗时、错误率),可集成 Prometheus。 3. 分布式追踪 :在微服务环境中,为 MCP 请求注入追踪 ID。 |
5.4 常见问题排查
在开发和集成 MCP 服务器时,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 检查与解决步骤 |
|---|---|---|
| Claude Desktop 无法加载服务器,提示 “Failed to start server” | 1. 配置文件路径错误。 2. command 或 args 配置错误。 3. 服务器代码有语法错误,启动即崩溃。 4. 权限不足。 |
1. 检查 claude_desktop_config.json 路径和格式是否正确。 2. 在终端手动运行配置中的 command 和 args ,看能否启动服务器。 3. 查看 Claude Desktop 的日志文件(位置因系统而异)获取详细错误。 4. 确保 Node.js 和 tsx 已正确安装且在 PATH 中。 |
| Claude 无法发现资源或工具 | 1. 服务器 capabilities 未正确声明。 2. resources/list 或 tools/list 的请求处理器未设置或报错。 3. 初始化握手失败。 |
1. 在服务器启动后,检查是否有初始化成功的日志。 2. 在 list 请求处理器中添加日志,确认其被调用。 3. 使用 MCP 协议的调试工具或客户端进行逐步排查。 |
| 工具调用返回 “Internal error” 或超时 | 1. 工具处理函数抛出未捕获的异常。 2. 工具执行的操作本身耗时过长或阻塞。 3. 输入参数不符合 inputSchema 。 |
1. 在工具调用处理函数中添加 try-catch ,并返回格式化的错误信息。 2. 为耗时操作(如网络请求)添加超时控制。 3. 在客户端(如 Claude)查看工具调用时的具体参数是否符合预期。 |
| 资源内容显示乱码或格式错误 | 1. mimeType 设置错误。 2. 返回的 text 或 blob 格式与声明的 mimeType 不匹配。 |
1. 确保 mimeType 正确,如 JSON 数据用 application/json 。 2. 对于非文本资源,可能需要使用 blob 字段并 base64 编码。 |
6. 扩展方向与最佳实践
掌握了基础 MCP 服务器的构建后,你可以向以下几个方向深入:
1. 开发实用的 MCP 服务器:
- 数据库服务器 :暴露安全的查询工具(参数化查询)和表结构资源。
- 项目管理服务器 :集成 Jira、Asana、Linear,提供创建任务、查询状态等工具。
- 代码仓库服务器 :集成 Git,提供搜索代码、读取文件、创建分支等工具。
- 内部知识库服务器 :连接 Confluence、Notion,将文档作为资源提供给 AI。
2. 遵循最佳实践:
- 单一职责 :一个 MCP 服务器只负责一个领域(如只做 GitHub 集成,不要混入 Jira)。这符合 Unix 哲学,也便于维护和复用。
- 完整的文档 :为你的服务器编写清晰的
README,说明其提供的资源、工具、配置方法和安全须知。 - 版本化 :通过清单中的
version字段管理版本。对inputSchema的破坏性变更需要升级主版本号。 - 测试 :为你的服务器编写单元测试和集成测试。可以模拟 MCP 客户端发送请求来验证功能。
3. 探索客户端开发: MCP 的魅力在于双向开放。除了开发服务器,你还可以开发自己的 MCP 客户端,将 AI 能力嵌入到你的应用中。客户端 SDK 同样提供了连接服务器、管理会话、调用工具和读取资源的标准方法。
MCP 开放标准为 AI 应用的可组合性打开了新的大门。它通过清晰的协议将 AI 模型与外部能力解耦,让开发者能够专注于构建高质量、安全的“能力适配器”,而无需担心与每个 AI 平台的集成细节。从今天构建一个简单的系统信息服务器开始,逐步深入到更复杂的业务集成,你将能更高效地打造出真正智能、能安全操作数字世界的 AI 助手。
更多推荐



所有评论(0)