MCP 协议深入实战:从入门到编写自定义 MCP Server
引言
2024 年底,Anthropic 开源的 Model Context Protocol (MCP) 迅速成为 AI 开发社区的热门话题。它定义了 AI 客户端与大模型之间标准化的工具调用和上下文交换协议,被业界称为"AI 领域的 USB-C 接口"。
截至 2026 年中期,MCP 已经发展到了 v1.2 版本,获得了 OpenAI、Google、JetBrains 等主流厂商的官方支持。VS Code、Cursor、Windsurf、JetBrains IDE 等开发工具都已原生集成 MCP 协议。
本文将带你从零开始构建一个完整的 MCP Server,涵盖协议核心概念、SDK 使用、调试技巧和生产部署的最佳实践。
MCP 协议核心概念
协议架构
MCP 采用客户端-服务端架构,通信基于 JSON-RPC 2.0 协议:
┌─────────────────┐ JSON-RPC 2.0 ┌─────────────────┐
│ │ ════════════════════════ │ │
│ AI 客户端 │ ← request/response → │ MCP Server │
│ (Cursor/IDE) │ ← notification/event → │ (自定义工具) │
│ │ │ │
└─────────────────┘ └─────────────────┘
核心概念
| 概念 | 说明 |
|---|---|
| Tool | 可被 LLM 调用的函数,类似 Function Calling |
| Resource | 可被读取的数据源(文件、API、数据库) |
| Prompt | 预定义的可复用提示词模板 |
| Transport | 传输层,支持 stdio(本地)和 SSE(远程) |
| Session | 客户端与服务端之间的连接会话 |
环境准备
安装 MCP SDK
MCP 官方 SDK 支持 TypeScript 和 Python。我们以 TypeScript 为例:
# 创建项目
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
# 也可以使用 pnpm
pnpm add @modelcontextprotocol/sdk zod
项目结构
my-mcp-server/
├── src/
│ ├── index.ts # 入口
│ ├── tools/ # 工具实现
│ │ ├── git.ts
│ │ ├── weather.ts
│ │ └── search.ts
│ └── utils.ts # 工具函数
├── package.json
├── tsconfig.json
└── README.md
编写第一个 MCP Server
1. 基础骨架
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 创建 MCP Server 实例
const server = new McpServer({
name: "my-tools",
version: "1.0.0",
description: "我的第一个 MCP 工具服务",
});
// 启动服务
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server 已启动");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
2. 注册工具(Tool)
Tool 是最核心的功能。让我们注册一个代码审查工具:
server.tool(
"code-review",
"对指定代码进行自动化代码审查",
{
code: z.string().describe("待审查的代码"),
language: z.string().describe("编程语言,如 typescript, python, rust"),
severity: z.enum(["all", "error", "warning"]).default("all").describe("审查级别"),
},
async ({ code, language, severity }) => {
// 模拟代码审查逻辑
const issues = [];
if (code.includes("var ")) {
issues.push({ line: "N/A", type: "warning", message: `使用 let/const 替代 var` });
}
if (code.length > 500) {
issues.push({ line: "N/A", type: "warning", message: "函数过长,建议拆分为多个小函数" });
}
if (code.includes("console.log")) {
issues.push({ line: "N/A", type: "info", message: "生产环境建议移除 console.log" });
}
if (code.includes("any") && language === "typescript") {
issues.push({ line: "N/A", type: "error", message: "避免使用 any 类型,改用具体类型" });
}
return {
content: [
{
type: "text",
text: JSON.stringify({
language,
totalIssues: issues.length,
issues: severity === "all" ? issues : issues.filter(i => i.type === severity),
score: Math.max(0, 100 - issues.length * 15),
}, null, 2),
},
],
};
}
);
3. 添加 Resource(资源)
Resource 让 LLM 可以直接读取数据,类似可读的文件系统:
server.resource(
"project-config",
"project://config",
{
description: "项目配置文件",
mimeType: "application/json",
},
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
name: "my-project",
version: "2.0.0",
engines: { node: ">=18" },
scripts: {
dev: "vite",
build: "tsc && vite build",
test: "vitest",
},
}, null, 2),
},
],
})
);
4. 注册 Prompt 模板
Prompt 可以让用户一键生成高质量的上下文:
server.prompt(
"commit-message",
"生成符合 Conventional Commits 规范的提交信息",
{
type: z.enum(["feat", "fix", "chore", "docs", "refactor", "test"]).describe("提交类型"),
scope: z.string().optional().describe("影响范围"),
description: z.string().describe("变更描述"),
},
({ type, scope, description }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `生成一个 Conventional Commits 提交信息:\n类型: ${type}\n范围: ${scope || "无"}\n描述: ${description}\n\n格式: (): `,
},
},
],
})
);
本地调试与测试
方案一:MCP Inspector(官方调试工具)
# 全局安装 MCP Inspector
npx @modelcontextprotocol/inspector \
node dist/index.js
# 或指定 Node 参数
npx @modelcontextprotocol/inspector \
--env NODE_OPTIONS="--loader ts-node/esm" \
node src/index.ts
Inspector 提供 Web 界面,可以:
- 列出所有注册的 Tool / Resource / Prompt
- 手动调用工具并查看返回结果
- 查看 JSON-RPC 通信日志
- 模拟客户端连接行为
方案二:使用 curl 测试(自建 HTTP 服务)
如果使用 SSE Transport,可以直接用 curl 测试:
# 发送工具调用请求
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "code-review",
"arguments": {
"code": "var x = 1",
"language": "typescript"
}
}
}'
集成到 AI 客户端
Cursor 配置
// .cursor/mcp.json
{
"mcpServers": {
"my-tools": {
"command": "node",
"args": ["dist/index.js"],
"env": {
"NODE_ENV": "production"
}
}
}
}
VS Code 配置
// .vscode/mcp.json
{
"servers": {
"my-tools": {
"type": "stdio",
"command": "node",
"args": ["dist/index.js"]
}
}
}
Claude Desktop 配置
// claude_desktop_config.json
{
"mcpServers": {
"my-tools": {
"command": "node",
"args": ["-e", "require('./dist/index.js')"],
"env": {}
}
}
}
高级模式:SSE Transport
本地开发常用 stdio,但生产环境需要远程调用。SSE(Server-Sent Events)Transport 支持远程连接:
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const app = express();
const server = new McpServer({
name: "remote-tools",
version: "1.0.0",
});
// SSE 端点
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
// 消息端点
app.post("/messages", async (req, res) => {
// 处理传入的 JSON-RPC 消息
res.json({ ok: true });
});
app.listen(3000);
console.log("MCP Server (SSE) running on port 3000");
生产部署最佳实践
1. 日志与监控
- 所有 stderr 输出自动被客户端捕获,利用这个特性做日志
- 推荐使用
pino等结构化日志库 - 监控调用次数、延迟、错误率
2. 输入验证
- 使用 Zod 进行严格的输入校验
- 不要信任 LLM 传入的参数
- 设置合理的超时和限流
3. 错误处理
server.tool(
"fetch-url",
"获取 URL 内容",
{ url: z.string().url() },
async ({ url }) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const text = await response.text();
return { content: [{ type: "text", text }] };
} catch (error) {
return {
isError: true,
content: [{ type: "text", text: `错误: ${error.message}` }],
};
}
}
);
4. 安全审计
- 最小权限原则:只暴露必要的能力
- 对文件系统操作添加路径白名单
- 对网络请求限制 IP 和域名范围
- 敏感操作添加二次确认(通过 Prompt)
const ALLOWED_PATHS = ["/home/user/projects", "/tmp"];
const ALLOWED_DOMAINS = ["api.github.com", "registry.npmjs.org"];
server.tool(
"read-file",
"安全的文件读取",
{
path: z.string().refine(p => ALLOWED_PATHS.some(a => p.startsWith(a)), {
message: "路径不在白名单中",
}),
},
async ({ path }) => {
// 安全读取
}
);
实战案例:Git 工作流工具
让我们实现一个实用的 Git MCP Server:
import { execSync } from "child_process";
server.tool(
"git-log",
"查看最近 Git 提交记录",
{
count: z.number().min(1).max(50).default(10),
branch: z.string().optional(),
},
async ({ count, branch }) => {
const branchOpt = branch ? ` ${branch}` : "";
const output = execSync(`git log --oneline --max-count=${count}${branchOpt}`, {
encoding: "utf-8",
});
return {
content: [{ type: "text", text: output }],
};
}
);
server.tool(
"git-diff",
"查看文件变更差异",
{
file: z.string().optional(),
staged: z.boolean().default(false),
},
async ({ file, staged }) => {
const stagedOpt = staged ? "--staged" : "";
const fileOpt = file ? ` -- "${file}"` : "";
const output = execSync(`git diff ${stagedOpt}${fileOpt}`, {
encoding: "utf-8",
});
return {
content: [{ type: "text", text: output }],
};
}
);
常见问题与调试
| 问题 | 原因 | 解决 |
|---|---|---|
| Tool 返回空结果 | 参数校验失败导致异常被吞 | 检查 Zod schema 是否正确 |
| Client 连接后无响应 | Transport 未正确初始化 | 确认走 stdio 还是 SSE,检查端口占用 |
| Tool 调用超时 | 异步操作时间过长 | 设置合理超时,或者将耗时操作拆分为多步 |
| 类型错误 | 参数类型与 Zod 定义不匹配 | LLM 可能生成错误类型,用 zod 做兜底解析 |
| SSE 连接断开 | HTTP 连接超时或代理问题 | 开启 keep-alive,审查反向代理配置 |
总结
MCP 协议正在成为 AI 工具调用的标准协议,掌握 MCP Server 开发能力意味着能够:
- 将任意业务逻辑暴露给 AI 客户端调用
- 构建自定义的代码分析、CI/CD、运维工具链
- 将私有数据源安全地接入 AI 工作流
随着更多厂商接入 MCP 协议,它的生态系统会越来越丰富。现在开始编写自己的 MCP Server,就是为 AI 时代的工具生态做好准备。
相关阅读: